Files
Kai Chappell 885196d933
All checks were successful
CI / Lint (push) Successful in 4s
CI / Type Check (push) Successful in 20s
CI / Test (push) Successful in 57s
CI / Release (push) Has been skipped
refactor(deploy): remove per-app tunnel in favour of central cloudflared
Routing now handled by central cloudflared + NPM on public-network.
2026-02-03 23:43:29 +00:00
..

py-dvt-ate Deployment

Deploy the DVT Simulation Platform dashboard with Cloudflare Tunnel for public access.

Idle Auto-Pause

The physics simulation automatically pauses when no one is viewing the dashboard.

Configuration:

# In .env or docker-compose.yml
IDLE_PAUSE_SECONDS=30  # Pause physics after 30s idle

Behaviour:

  • When someone views the dashboard, physics runs normally
  • After IDLE_PAUSE_SECONDS with no viewers, physics engine pauses
  • CPU drops to ~0% while paused
  • Physics resumes instantly when someone visits
  • Container stays running (no restart needed)

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
  2. Navigate to NetworksTunnels
  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)

# 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

# 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:

docker compose logs -f

4. Verify

# 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:

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:

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

cd /mnt/fast-pool/apps/portfolio-demos/py-dvt-ate/deploy
git pull
docker compose up -d --build

Stopping

docker compose down