Skip to main content

Production deployment checklist

This page covers the settings and first-run steps for deploying core-api with deployment/docker-compose.yml (started with docker compose --env-file .env.local up --build -d from the deployment/ directory). core-api owns users, passwords, sessions, MFA, invites, API keys and roles (see Identity and authentication), so a fresh database has no users until you create the first admin.

1. Pre-flight​

ItemRequirement
JWT_SECRETRequired outside ENVIRONMENT=local (>= 32 chars, not the dev default or containing change_me): openssl rand -hex 32. The app refuses to start otherwise. Changing it later signs everyone out.
MFA_ENCRYPTION_KEYRequired outside local (>= 32 chars): openssl rand -hex 32. Changing it later makes enrolled authenticators unusable.
DATABASE_URLMust include a password outside local. The compose file builds it from POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB.
DASHBOARD_URLThe public https URL of the dashboard. It is used in emailed links and makes session cookies Secure (override with COOKIE_SECURE).
CORS_ORIGINSMust include the dashboard origin.
TRUST_PROXY_HEADERSSet true only if a reverse proxy/tunnel in front of the API overwrites X-Forwarded-For. When it is false behind a proxy, every user appears to share the proxy's IP, so the per-IP login limit (50 per 5 min) is shared by everyone.
DOCS_PUBLIC_URLThe docs service serves this documentation site (static build behind nginx). It publishes no host port: route a reverse proxy on the compose network to docs:8080. DOCS_PUBLIC_URL is the origin readers reach it at through that proxy; it is baked into the build (canonical URLs, sitemap), so changing it needs up --build docs. The service has no dependencies and can be left out of a deployment.
Mail transportSMTP_HOST/SMTP_PORT must point to a real provider. The default Mailpit only captures mail, so password-reset, invite and sign-in-code emails would never arrive.

docker compose --env-file .env.local config fails fast if the two secrets are unset. The shipped deployment/.env.local carries placeholders that the app rejects on purpose.

2. Deploy and create the first admin​

  1. Bring the stack up. The migrator service runs alembic upgrade head and exits; the API starts after it succeeds. On start the API creates the platform superadmin role if it is missing (ensure_platform_roles in app/main.py).

  2. Create the first admin so someone can sign in:

    BOOTSTRAP_PASSWORD='<strong passphrase>' \
    uv run python scripts/bootstrap_admin.py --email [email protected]
    # or: mise run user:bootstrap-admin [email protected]

    Run it inside the API container (docker compose exec api ...) so it uses production settings. By default the user becomes a superadmin; use --tenant <id> --role <role key> to grant a tenant role instead, and --reset-password to change an existing user's password. The same script is the break-glass recovery path.

  3. Sign in, then invite users (/invites); they can also use Forgot password (needs working mail). Subscribers sign in with an emailed code.

3. Verification checklist​

  • GET /api/v1/health returns 200 and the API log shows no settings validation error.
  • POST /api/v1/auth/login for the bootstrap admin returns {"ok": true} with app.at/app.rt cookies scoped to the API host and Secure set.
  • GET /api/v1/me lists the expected roles (superadmin for the bootstrap admin) and permissions.
  • Trigger Forgot password for a test user and confirm the email actually arrives.
  • Enrol MFA on a test account, sign out and back in. Superadmins need MFA to use platform-admin endpoints (SUPERADMIN_REQUIRES_MFA).
  • Repeated failed logins from one address return 429 with Retry-After (and that address is the client's, not the proxy's - check TRUST_PROXY_HEADERS).

4. Policies worth knowing​

  • Passwords must be at least 12 characters (PASSWORD_MIN_LENGTH in app/core/security.py).
  • Five failed logins within 15 minutes lock an account for 15 minutes (LOCKOUT_* settings).
  • Tenant create/rename/delete and the console require a platform role such as superadmin; a tenant's own roles are managed by its tenant admins - see Roles and scopes.
  • New users are invited by emailed link; administrators never email passwords.