Deployment
Production uses Caddy, FastAPI, PostgreSQL, Valkey, and a private Umami/PostgreSQL analytics pair under Docker Compose. The compiled Svelte client and MkDocs site are copied into the Caddy image. The pipeline is build-time-only and has no production credentials.
The base docker-compose.yml is a credential-free local stack and binds ports 80 and 443 to
127.0.0.1. It must not be used by itself as a public deployment definition. Public deployments
layer docker-compose.production.yml on top.
Caddy serves /docs/ before the SPA fallback, proxies /api/, and upgrades the room WebSocket.
The graph bundle is downloaded from a versioned release and verified against its SHA-256 manifest
before the API is restarted.
The live Build Week deployment runs on a small Hetzner EU server. https://www.webwoven.org is the
canonical origin, https://webwoven.org permanently redirects to it, and
https://stats.webwoven.org publishes the self-hosted analytics tracker and private dashboard.
Creating or resizing billable infrastructure remains an explicit human decision.
Production configuration
Copy .env.production.example to .env and replace every placeholder with an unquoted value.
Generate URL-safe secrets, for example with openssl rand -hex 32. The browser origin, API origin,
and Caddy site address must all use HTTPS; the separate redirect address must name the non-canonical
HTTPS host; the session and edge secrets must be different; secure cookies must be enabled; and
WEBWOVEN_GRAPH_DIR must name an existing, checksum-verified bundle. Caddy permanently redirects
the redirect address to the canonical site while preserving the request path and query string.
The analytics address must be a third HTTPS host, normally https://stats.webwoven.org. Supply a
stable website UUID, the canonical browser hostname, and three distinct server-only secrets for the
Umami database, application signing, and administrator password. The deployment bootstraps Umami
over its loopback-only port before Caddy publishes the dashboard and tracker.
infra/scripts/deploy.sh validates these invariants without printing secrets, validates the merged
Compose model, and then deploys with both Compose files. The production override publishes ports
publicly and disables the demo graph fallback. A plain docker compose up remains local-only.
Database backups default outside the repository under the user's XDG state directory. The backup
script applies umask 077, creates a mode-700 directory, and keeps 14 days by default. If
BACKUP_DIR is intentionally set inside the checkout, backups/ remains ignored by Git and the
Docker build context.
The daily backup schedule must invoke the same backup runner once for postgres/webwoven and once
for analytics-db/umami. A separate daily retention job runs infra/scripts/prune-analytics.sh
with a 90-day window. See Analytics operations.