Skip to content

Production Checklist

Work through this before exposing an AnvilBase deployment. It consolidates the controls from the Security section into a single pre-flight list.

Secrets

  • Every secret is unique and randomly generated (openssl rand -hex 32+).
  • Secrets live in a secrets manager (Vault, AWS/GCP Secrets Manager), not a committed file.
  • .env is never in version control.
  • CONTROL_PLANE_SECRET ≥ 32 chars; SECRET_KEY_BASE ≥ 64 chars and not the public dev default (it signs realtime/Phoenix cookies & tokens — a known value forges sessions). Compose requires it (${SECRET_KEY_BASE:?}) and a prod realtime release refuses to boot on the dev literal.
  • MINIO_KMS_SECRET_KEY is an operator-managed key (not the dev default).
  • INTERNAL_SECRET is set, identical across control-plane/auth/realtime/deno, and not the public dev literal. It gates the control-plane /internal/* routes (incl. the per-project JWT-secret fetch), so a weak/known value is a full auth-bypass. The control-plane and a prod realtime release refuse to boot on the known dev literal; Compose requires it (${INTERNAL_SECRET:?}) and Helm requireds it — so this is enforced, but verify it’s a real secret.
  • A secret rotation schedule is defined (rotation).

Network

  • Only Traefik (ports 80/443) is exposed publicly; internal service ports are unpublished.
  • External TLS configured (Let’s Encrypt or your certs); HTTP → HTTPS.
  • Traefik dashboard disabled / not internet-reachable.
  • Services share a private network (anvilbase-net); Postgres/Valkey/MinIO are not directly exposed.
  • mTLS enabled between services (ANVILBASE_MTLS_ENABLED=true) once traffic leaves a single host.
  • Webhook SSRF guard relied upon (not disabled).

Database

  • RLS enabled on all user tables — verify: sql SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname='public';
  • Every table has a tested policy (RLS sandbox).
  • statement_timeout set (e.g. 30s) to bound runaway queries.
  • Per-project connection cap sized (ANVILBASE_PROJECT_POOL_MAX) under Postgres max_connections.
  • Application uses the anvilbase role, never the Postgres superuser.
  • Data volume encrypted at rest (LUKS / cloud volume encryption).

Postgres pg_hba hardening (existing deployments)

The raw-SQL exec_sql RPC already runs on a least-privileged authenticator connection that cannot reach another project’s database. As defense-in-depth, fresh Postgres inits also require scram (not trust) for the privileged anvilbase owner over loopback, so a hypothetical reached-anvilbase context cannot open a passwordless dblink hairpin into a sibling platform_<id> database. This auto-applies only on a fresh data dir. To apply it to an already-initialized deployment, prepend these two rules above the existing host all all 127.0.0.1/32 trust line in $PGDATA/pg_hba.conf (first match wins), then reload:

host all anvilbase 127.0.0.1/32 scram-sha-256
host all anvilbase ::1/128 scram-sha-256
SELECT pg_reload_conf();

API

  • Rate limits in effect (Valkey reachable — required for >1 replica).
  • Max body size bounded to prevent memory exhaustion.
  • API key scopes understood and enforced in client/server split.
  • JWT expiry set sensibly (default 3600s).
  • CORS restricted to your origins in production.
  • Management credentials (admin token / PATs) tightly held; treat each as deployment-admin (RBAC).

Storage

  • SSE-S3 encryption active (per-project keys via MINIO_KMS_SECRET_KEY).
  • Bucket isolation verified (bucket-<project_id> per project).
  • Max file size enforced; dangerous content types handled (content-type safety).

Auth

  • Password minimum length ≥ 8 (min_password_length).
  • Email verification required where appropriate; SMTP configured + tested.
  • OAuth client secrets stored encrypted (they are, by default — verify masking).
  • Magic-link / reset link expiry is short.

Backups & recovery

  • Logical backups scheduled (Backups & Restore).
  • PITR enabled if you need tight RPO (PITR).
  • A restore has been tested (not just configured) on a staging copy.
  • Backup encryption keys (WALG_LIBSODIUM_KEY) stored safely and separately.

Observability

  • Health checks wired (/health, /health/services) to your uptime monitor.
  • Logs aggregated (structured JSON) and searchable.
  • Alerts on: 5xx error rate, auth failure spikes, DB connection saturation, storage near quota, cert expiry, and the rate-limiter degraded warning (Monitoring, Scaling).
  • Audit log export scheduled to immutable off-box storage if required for compliance (Audit Logs).

Upgrades

  • Component versions pinned (Compose tags / Helm anvilbaseVersion).
  • Upgrade + rollback procedure rehearsed (Upgrades).

When every box is checked, you’re ready. Keep this list with your runbooks and re-run it after major changes.

Next: Backups & Restore.