Add idle auto-shutdown configuration to deployment

- IDLE_TIMEOUT_MINUTES env var (default 30 min)
- restart: no policy so container stays stopped
- Optional wakeup service for auto-restart
- Document three restart options in readme
This commit is contained in:
2026-01-29 21:09:53 +00:00
parent b7663d5a31
commit cc5a8191b0
4 changed files with 301 additions and 0 deletions

8
deploy/.env.example Normal file
View File

@@ -0,0 +1,8 @@
# Cloudflare Tunnel token
# Get this from: Cloudflare Zero Trust > Networks > Tunnels > Create > Docker
CLOUDFLARE_TUNNEL_TOKEN=your-tunnel-token-here
# Idle auto-shutdown (minutes)
# App exits after this many minutes with no active viewers
# Set to 0 to disable (app runs forever)
IDLE_TIMEOUT_MINUTES=30

64
deploy/docker-compose.yml Normal file
View File

@@ -0,0 +1,64 @@
services:
streamlit:
build:
context: ..
dockerfile: Dockerfile
container_name: py-dvt-ate-streamlit
restart: "no" # Don't auto-restart - we want it to stay stopped when idle
environment:
- IDLE_TIMEOUT_MINUTES=${IDLE_TIMEOUT_MINUTES:-30}
expose:
- "8080"
volumes:
- ./data:/app/data
networks:
- dvt-ate
nginx:
image: nginx:alpine
container_name: py-dvt-ate-nginx
restart: unless-stopped
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
expose:
- "80"
depends_on:
- streamlit
networks:
- dvt-ate
cloudflared:
image: cloudflare/cloudflared:latest
container_name: py-dvt-ate-tunnel
restart: unless-stopped
command: tunnel run
environment:
- TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
depends_on:
- nginx
networks:
- dvt-ate
# Optional: Auto-start streamlit when stopped (checks every 30s)
# Uncomment if you want fully automatic restart on visit
# wakeup:
# image: docker:cli
# container_name: py-dvt-ate-wakeup
# restart: unless-stopped
# entrypoint: /bin/sh
# command:
# - -c
# - |
# while true; do
# sleep 30
# if ! docker inspect -f '{{.State.Running}}' py-dvt-ate-streamlit 2>/dev/null | grep -q true; then
# echo "[$(date)] Starting streamlit..."
# docker start py-dvt-ate-streamlit
# fi
# done
# volumes:
# - /var/run/docker.sock:/var/run/docker.sock:ro
networks:
dvt-ate:
driver: bridge

45
deploy/nginx.conf Normal file
View File

@@ -0,0 +1,45 @@
events {
worker_connections 1024;
}
http {
upstream streamlit {
server streamlit:8080;
}
server {
listen 80;
server_name _;
# Remove X-Frame-Options to allow iframe embedding
proxy_hide_header X-Frame-Options;
# Allow embedding from your domain
add_header Content-Security-Policy "frame-ancestors 'self' https://kschappell.com https://*.kschappell.com";
location / {
proxy_pass http://streamlit;
proxy_http_version 1.1;
# WebSocket support (required for Streamlit)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Standard proxy headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts for long-running connections
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}
# Health check endpoint
location /health {
return 200 'ok';
add_header Content-Type text/plain;
}
}
}

184
deploy/readme.md Normal file
View File

@@ -0,0 +1,184 @@
# py-dvt-ate Deployment
Deploy the DVT Simulation Platform dashboard with Cloudflare Tunnel for public access.
## Idle Auto-Shutdown
The app automatically shuts down after a period of inactivity to save resources.
**Configuration:**
```bash
# In .env or docker-compose.yml
IDLE_TIMEOUT_MINUTES=30 # Shutdown after 30 min idle (0 = disabled)
```
**Behaviour:**
- App tracks activity when someone has the dashboard open
- After `IDLE_TIMEOUT_MINUTES` with no viewers, the container exits
- nginx will show a 502 error until the container is restarted
**Restart Options:**
1. **Manual restart** (simplest):
```bash
docker start py-dvt-ate-streamlit
```
2. **Auto-restart on poll** (uncomment `wakeup` service in docker-compose.yml):
- Checks every 30 seconds if streamlit is stopped
- Automatically restarts it
- Adds ~30 second delay before app is available
3. **Always running** (set `IDLE_TIMEOUT_MINUTES=0`):
- App never auto-shuts down
- Uses minimal CPU when idle (~0.1%)
- Memory stays allocated (~300-400MB)
## Architecture
```
Internet
Cloudflare Edge (dvt-demo.kschappell.com)
↓ (tunnel)
cloudflared container
nginx container (WebSocket proxy + header handling)
streamlit container (port 8080)
```
## Directory Structure
```
/mnt/fast-pool/apps/portfolio-demos/py-dvt-ate/
├── docker-compose.yml
├── nginx.conf
├── .env
└── data/ # Persistent storage (created automatically)
├── py_dvt_ate.db # SQLite database
├── measurements/ # Test measurement files
└── reports/ # Generated PDFs
```
## Setup
### 1. Create Cloudflare Tunnel
1. Go to [Cloudflare Zero Trust Dashboard](https://one.dash.cloudflare.com/)
2. Navigate to **Networks** → **Tunnels**
3. Click **Create a tunnel**
4. Select **Cloudflared** as the connector
5. Name it `py-dvt-ate` (or similar)
6. Copy the tunnel token (long string starting with `eyJ...`)
### 2. Configure Public Hostname
Still in the tunnel configuration:
1. Go to the **Public Hostname** tab
2. Add a public hostname:
- **Subdomain:** `dvt-demo`
- **Domain:** `kschappell.com`
- **Service Type:** `HTTP`
- **URL:** `nginx:80`
This routes `dvt-demo.kschappell.com` → nginx container → Streamlit app.
### 3. Deploy
The build requires access to the full py-dvt-ate source code. Two options:
**Option A: Clone repo to TrueNAS (recommended)**
```bash
# Clone repo to apps directory
cd /mnt/fast-pool/apps/portfolio-demos
git clone https://gitea.kschappell.com/kschappell/py-dvt-ate.git
# Create data directory and .env
cd py-dvt-ate/deploy
mkdir -p data
cp .env.example .env
nano .env # Add your CLOUDFLARE_TUNNEL_TOKEN
# Build and start
docker compose up -d --build
```
**Option B: Build image locally, transfer to TrueNAS**
```bash
# On development machine
cd /path/to/py-dvt-ate
docker build -t py-dvt-ate:latest .
docker save py-dvt-ate:latest | gzip > py-dvt-ate.tar.gz
# Transfer to TrueNAS, then:
docker load < py-dvt-ate.tar.gz
# Update docker-compose.yml to use image instead of build:
# streamlit:
# image: py-dvt-ate:latest
```
**Check logs:**
```bash
docker compose logs -f
```
### 4. Verify
```bash
# Check tunnel is connected (in Cloudflare dashboard, tunnel should show "Healthy")
# Test the endpoint
curl https://dvt-demo.kschappell.com/health
# Test iframe headers
curl -I https://dvt-demo.kschappell.com | grep -i frame
# Should NOT show X-Frame-Options (we strip it)
```
## Troubleshooting
### Tunnel not connecting
Check cloudflared logs:
```bash
docker compose logs cloudflared
```
Common issues:
- Invalid token (regenerate in Cloudflare dashboard)
- Network/firewall blocking outbound connections
### Streamlit not loading in iframe
Check nginx is stripping headers:
```bash
curl -I https://dvt-demo.kschappell.com
```
Should see:
- No `X-Frame-Options` header
- `Content-Security-Policy: frame-ancestors 'self' https://kschappell.com ...`
### WebSocket errors
Check browser console for WebSocket connection failures. Ensure nginx WebSocket config is correct and timeouts are sufficient.
## Updating
```bash
cd /mnt/fast-pool/apps/portfolio-demos/py-dvt-ate/deploy
git pull
docker compose up -d --build
```
## Stopping
```bash
docker compose down
```