Skip to content

Production deployment

The service is intentionally single-container: FastAPI serves the API and public pages, SQLite stores relational data, and LMDB stores short-lived OAuth/session state. Run one application worker against one persistent data volume.

Checklist

  • Set APP_ENV=production.
  • Inject JWT_SECRET_KEY and TOKEN_ENCRYPTION_KEY from a secrets manager; never bake them into an image or commit an environment file.
  • Configure exact public URLs and CORS_ALLOWED_ORIGINS.
  • Terminate TLS at the platform or reverse proxy.
  • Mount /app/data on encrypted, backed-up persistent storage. LMDB contains refresh-session records and other short-lived authentication artifacts.
  • Keep exactly one application replica and one Uvicorn worker while using embedded storage, and prevent old/new revision overlap during deployment. The durable webhook queue is resumed by that single worker after restarts.
  • Run alembic upgrade head once before application startup. The image does this automatically, but platforms that can start multiple containers must serialize that migration step or disable overlapping revisions.
  • Configure only the OAuth providers you operate and register each exact callback.
  • Scan and smoke-test the built image before deployment.

Container

docker build --target runtime -t subconscious-api:latest .
docker run --rm -p 8000:8000 \
  --env-file /etc/subconscious/prod.env \
  -v subconscious-data:/app/data \
  subconscious-api:latest

The image runs as an unprivileged user. Check /health for liveness and /health/ready for embedded-store readiness.

Azure Container Apps

The checked-in workflow deploys an immutable image to the existing subconscious-prod container app and polls /health/ready before reporting success; it does not provision or verify the app's persistent runtime configuration. GitHub Actions are pinned to reviewed commit SHAs. Before enabling automatic production deployment, verify in Azure that:

  • /app/data is an encrypted persistent volume mounted read/write by UID 1000;
  • minimum and maximum replicas are both one and revision overlap is disabled;
  • ingress targets port 8000, probes use /health and /health/ready, and TRUSTED_HOSTS includes every public/probe Host header;
  • JWT, Fernet, OAuth, and SMTP values come from Container Apps secrets;
  • the app can pull the private GHCR image; and
  • DATABASE_URL is SQLite as packaged. PostgreSQL requires adding and testing an async PostgreSQL driver before deployment.

The application does not trust arbitrary forwarded client-IP headers. Configure rate limiting at the trusted Azure ingress as well as in the application, rather than enabling broad proxy trust. A failed readiness check intentionally does not perform an automatic image rollback: an older image may be incompatible after a forward database migration. Retain backups and use an operator-reviewed rollback or forward fix after checking schema compatibility.

Webhook destinations must use HTTPS and resolve only to public IP addresses. Restrict outbound container egress as an additional SSRF defense.

Legacy data checks

The baseline migration is intentionally non-destructive. Before upgrading a pre-Alembic database, take a backup and inspect it for case-insensitive duplicate email addresses and OAuth/API-key rows for providers that are no longer enabled. Resolve collisions and revoke obsolete provider credentials deliberately; the migration does not delete user data automatically.

Changing JWT_ISSUER, JWT_AUDIENCE, or the signing key invalidates existing access tokens. Coordinate that change with native clients, which must implement the authorization-code plus S256 PKCE callback contract documented here.

Key rotation

Set a newly generated Fernet key as TOKEN_ENCRYPTION_KEY and the previous key as TOKEN_ENCRYPTION_KEY_OLD. Existing connected-provider tokens remain readable during a staged rotation. Keep the old key until accounts have been reauthorized; the service does not currently provide bulk re-encryption.