Skip to content

Production Deployment Security Checklist

Before deploying Patrimoine to production, verify each item below.

  • Set DEBUG=false in backend/container/.env — disables error trace display. Cookie Secure flag is protocol-aware (not controlled by DEBUG).
  • Set E2E_TEST_MODE=false (or leave unset) in backend/container/.env — prevents auth bypass.
  • Set REGISTRATION_ENABLED=false (default) — only enable during initial setup, then disable.
  • Make sure you have an admin account, so you can invite additional users without re-enabling public registration. With OIDC, set ADMIN_EMAILS (or ADMIN_SUBS) to your own account. With local auth those variables do not apply: the first account registered on an empty instance becomes admin — see Troubleshooting.
  • Generate a strong JWT_SECRET (the app refuses to start below 32 characters, but that floor is a minimum, not a target — use the full 48-byte generator):
    Terminal window
    python -c "import secrets; print(secrets.token_urlsafe(48))"
  • Leave EXPOSE_API_DOCS unset (default false) so the interactive API docs and /openapi.json are not published.
  • If you enable /metrics, generate METRICS_TOKEN with the 48-byte generator and do not publish the backend port; Prometheus must scrape the backend on the container network.
  • If the backend is reachable by anything other than the frontend’s nginx (separate hosts, published port), set TRUSTED_PROXIES; behind your own proxy set FRONTEND_TRUSTED_PROXIES to its address; firewall the backend port.
  • Once all pre-upgrade sessions have expired, set JWT_ACCEPT_LEGACY_KEY=false.
  • If you use Redis, keep it on a private network (the podman.sh managed Redis listens on the pod loopback only) and set a password in REDIS_URL otherwise.
  • Review LOGIN_MAX_ATTEMPTS (default 5) and LOGIN_LOCKOUT_MINUTES (default 15) — local-auth login/TOTP lockout thresholds. The defaults are reasonable for most deployments; tighten LOGIN_LOCKOUT_MINUTES if you want failed attempts to cost an attacker more time.
  • Use HTTPS — terminate TLS at a reverse proxy (Traefik, Caddy, or nginx).
  • Set FRONTEND_URL and OIDC_REDIRECT_URI to HTTPS URLs.
  • Ensure Uvicorn runs with --proxy-headers (default in the backend Containerfile) so X-Forwarded-Proto/X-Forwarded-Host from the reverse proxy are trusted. Cookie Secure/SameSite attributes depend on this header for HTTPS detection.
  • If using Traefik: disable the compress middleware for /auth/ paths — it can strip Set-Cookie headers from 302 redirect responses, breaking the OIDC callback.
  • Use a strong, unique OIDC_CLIENT_SECRET.
  • Restrict the OIDC provider to your organization (e.g., Authentik application access policy).
  • Set up regular SQLite database backups (backup-db.sh with cron).
  • Configure offsite backup sync (WebDAV or S3) if using backup-db.sh.
  • On PostgreSQL, schedule pg_dump instead: backup-db.sh is SQLite-only.
  • Keep data/backups/ (internal AUTO_BACKUP files) pruned: the app never deletes them.
  • Consider filesystem-level encryption for the data/ volume.
  • Keep Python dependencies updated (uv lock --upgrade).
  • Keep frontend dependencies updated (npm update).
  • Configure CI secrets for image builds: PRIVATE_REGISTRY_USERNAME, PRIVATE_REGISTRY_PASSWORD, NTFY_TOKEN (secret) + NTFY_URL (variable).
  • Verify Forgejo repository settings restrict the GITHUB_TOKEN to read-only (Settings → Actions → Token permissions).
  • Verify the rocky runner image is up to date (quay.io/rockylinux/rockylinux:9-minimal — includes python3-pip, git, buildah, podman, sudo).