Operations · Deployment
Deployment & upgrade
How to deploy the backend to production, where the static surfaces live, what the host must provide, and how to upgrade safely.
Production deployment guide
Run the backend container behind a TLS-terminating reverse proxy. The app is stateless except for its SQLite database (and the in-memory registry), so scaling is "more replicas behind a load balancer", with the datastore caveat below.
Dockerfile · docker-compose.yml · api/routes.py · auth/config.py
flowchart TB
subgraph EDGE["Edge (your responsibility)"]
LB["Load balancer / reverse proxy
TLS · rate limit · timeouts · WAF"]
end
subgraph BACKEND["Backend host(s)"]
A1["uvicorn worker(s)
vailidator.api.routes:app :8000"]
DB[("SQLite volume (DB_PATH)
or Postgres via DATABASE_URL")]
end
subgraph STATIC["Static surfaces (separate)"]
V["Docs / marketing site → Vercel (demo/)"]
NG["Public Proof Verifier → nginx / Cloudflare Pages / GitHub Pages"]
end
USERS["Clients · extension · SDK · dashboards"] --> LB --> A1 --> DB
USERS --> STATIC
Production shape, self-hosted backend behind your edge; static surfaces hosted separately. Grounded in Dockerfile + vercel.json + deployment/.Required production settings (all via env):
env
VERIFAIER_ENV=production APP_ENV=production SESSION_SECRET=<64-hex random> # python -c "import secrets; print(secrets.token_hex(32))" AUTH_COOKIE_SECURE=true # HTTPS only AUTH_COOKIE_SAMESITE=lax # or strict CORS_ORIGINS=["https://app.example.com"] # restrict from the default ["*"] DB_PATH=/app/data/vailidator.db # on a persistent volume # providers: leave mock, or enable+key+live per ops-config#providers
Hard startup gate. With
AUTH_COOKIE_SECURE=true, the app refuses to start if SESSION_SECRET is unset/ephemeral (V-F3). Set the secret before enabling secure cookies. Missing secrets otherwise print [SECURITY] warnings to stderr at startup.- Proxy owns the edge: TLS, HSTS, general rate limiting, per-request timeouts and WAF live at your reverse proxy, the app sets response security headers but does not terminate TLS or throttle general traffic.
- Workers: run multiple uvicorn workers/replicas behind the LB for throughput. Keep one SQLite writer per node, or move to Postgres for shared multi-node state.
- No managed control plane: there is no VerifAIer-hosted SaaS. You own the deployment lifecycle.
Vercel / static site deployment
The docs + marketing site (demo/) is pure static HTML and deploys to any static host. vercel.json is configured for Vercel:
config
# vercel.json (real): serve demo/ as static
outputDirectory: "demo"
cleanUrls: true, trailingSlash: false
headers: Cache-Control per path + X-Content-Type-Options / X-Frame-Options /
Referrer-Policy / Permissions-Policy on /(.*)
| Surface | Host | Backend? |
|---|---|---|
Docs / marketing site (demo/) | Vercel (vercel.json) or any static host | no, static only |
| Public Proof Verifier (single-file) | nginx / Cloudflare Pages / GitHub Pages (deployment/) | no, zero backend |
| Operator & Control Center dashboards | Streamlit containers (Docker) | calls the API via API_URL |
The FastAPI backend does not run on Vercel. Vercel (and the
deployment/ nginx/Cloudflare/GitHub-Pages configs) host static surfaces only. The API must run as a container/uvicorn process on your own infrastructure. The deployment/ nginx and Cloudflare configs specifically serve the zero-backend Public Proof Verifier with a strict CSP.Backend hosting requirements
| Requirement | Detail |
|---|---|
| Runtime | Python 3.12, the version CI runs (or the provided python:3.12-slim container). 3.13 and 3.14 are exercised by hand; below 3.12 is refused by requires-python. See Install. |
| Dependencies | fastapi · uvicorn · anthropic · pydantic · pydantic-settings · jsonschema (see config) |
| Persistent storage | a writable volume for the SQLite file at DB_PATH (or a Postgres instance via DATABASE_URL) |
| Network egress | none required: outbound only if you enable a live provider (or optional blockchain anchoring) |
| Ports | 8000 (API); 8501/8502 if running the dashboards |
| Process | 1+ uvicorn workers; front with a reverse proxy for TLS + throttling |
| Health | GET /health for readiness/liveness |
| Privileges | runs non-root (appuser); no special capabilities |
Upgrade guide
Upgrades are additive by design, the API is versioned under /api/v1 and response bodies only grow (new optional fields). Procedure:
bash
# 1. back up the database first (see ops-resilience#backup) cp "$DB_PATH" "$DB_PATH.$(date +%Y%m%d).bak" # or snapshot the volume # 2a. containerized: pull/rebuild and recreate git pull && docker compose build && docker compose up -d # 2b. local install: reinstall the package git pull && pip install -e ".[dev]" # 3. smoke test the new version curl -s http://localhost:8000/health # confirm it responds
- Schema migrations: database schema/migrations live under
src/vailidator/db/migrationsanddb/schema; apply per your migration runner before serving traffic. - Rollback: keep the previous image tag and the pre-upgrade DB backup; roll back by redeploying the old tag and restoring the file.
- In-memory state: the trust registry is in-memory and is rebuilt from observations after a restart, expect it to be empty immediately post-upgrade until agents are re-observed.
Run the smoke tests and the release checklist after every upgrade.