The demo is a sandboxed instance of the production image that serves
synthetic cluster/VM/metrics data and blocks every mutating API call. It
backs the Launch Demo CTA on the marketing site (proxmoxvex-landing).
How it works
PROXMOXVEX_DEMO_MODE=trueswitches the app into demo mode at startup:ProxmoxVEx/demo/seed.pyprovisions thedemouser (flaggedis_demo=1,auth_source='demo'), a syntheticdemocluster, alert rules, migration history, and mock PBS/vCenter/NetApp records.ProxmoxVEx/demo/cluster_mock.pyintercepts the cluster manager sohost='demo'is answered fromfixtures/demo/*.json— no real hypervisor is ever contacted.ProxmoxVEx/demo/guard.pyinstalls abefore_requesthook that rejects every mutating/api/*call with403 {"error": "demo_forbidden"}except a small allow-list (login/logout, demo token exchange, health, SSE token, preferences).
- Public entry points:
GET /api/demo/auto-login— one-click entry; issues a single-use signed token, creates a session for the demo user, redirects to/.GET /api/demo/token+POST /api/demo/login— token exchange flow.GET /api/healthreturnsdemo_mode,mode,reset_in_secondsandcapacitywhen demo mode is active.GET /api/configreturnsdemo_mode,demo_read_onlyanddemo_reset_in_secondsso the UI can render the banner and disable destructive actions.
Running locally
docker compose -f docker-compose.yml -f docker-compose.demo-override.yml up --build -d
The demo listens on the normal app port; point the landing page at it via
PUBLIC_DEMO_ORIGIN.
Environment variables
| Variable | Default | Purpose |
|---|---|---|
PROXMOXVEX_DEMO_MODE |
false |
Enables the sandbox. |
PROXMOXVEX_DEMO_TOKEN_SECRET |
demo-local-secret |
HMAC secret for demo launch tokens. Set a real random value in production. |
PROXMOXVEX_DEMO_RESET_INTERVAL |
900 |
Seconds between automatic resets (reset sidecar SIGTERMs the app container; Postgres volume is re-seeded on boot). |
PROXMOXVEX_DEMO_ALLOWED_ORIGINS |
landing origins | CORS allow-list so the landing page can query /api/health. |
Isolation and security
- The demo stack runs on a dedicated
proxmoxvex-democompose network with its ownpostgres-demoservice and anonymous volume; no production volumes, TLS material, orconfig/contents are mounted. - All mutating API routes are blocked server-side; the demo account’s read coverage comes from its role while the guard — not RBAC — enforces read-only behaviour.
- Demo launch tokens are HMAC-signed, short-lived and single-use
(
ProxmoxVEx/demo/token.py). - The standard security headers (CSP,
X-Frame-Options,Referrer-Policy, HSTS behind TLS) apply unchanged; CORS is limited toPROXMOXVEX_ALLOWED_ORIGINS. DEMO_SEED_VERSIONinProxmoxVEx/demo/__init__.pytracks the synthetic dataset revision; bump it wheneverfixtures/demo/or the seed data changes so resets re-seed cleanly.
Production deployment
scripts/deploy-demo.sh and the pipeline build the image, set
PROXMOXVEX_IMAGE_TAG, and bring the stack up behind the
pmvexdemo.* tunnel. The demo-smoke job in
.github/workflows/docker.yml runs scripts/demo-smoke.sh to verify
/api/health reports demo_mode=true on every release.
Verification
- Contract/unit tests:
pytest tests/unit tests/contract -q(test_demo_token.py,test_demo_blocklist.py,test_demo_health.py,test_demo_guard.py). - E2E:
cd proxmoxvex-landing && npx playwright test— thedemo-sandbox/demo-resetspecs requireDEMO_ORIGINpointing at a running sandbox.