A production self-hosted deployment should be boring: one admin app, one database, durable storage, and HTTPS.
Reference shape
@cms0/adminrunning as a Node/Next.js app- managed Postgres or a backed-up Postgres container
- S3-compatible storage for uploads and snapshots, or a persistent filesystem volume
- SMTP or Plunk for email
- reverse proxy with TLS
Do not deploy with disposable filesystem storage unless uploads, snapshots, and backups are written to a durable mounted volume.
Docker quickstart
cms0 includes a local-first Docker Compose stack for @cms0/admin and Postgres.
cp deploy/docker/admin.env.example deploy/docker/admin.envEdit deploy/docker/admin.env and set at least:
CMS0_PUBLIC_APP_URL=http://localhost:3000
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=replace-with-a-long-random-secret
TRUSTED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
ADMIN_EMAIL=[email protected]
ADMIN_PASSWORD=replace-with-a-strong-password
ORG_NAME=cms0
CMS0_ASSET_BASE_URL=http://localhost:3000Compose intentionally fails if deploy/docker/admin.env is missing.
Then build and start the stack:
pnpm docker:admin:build
pnpm docker:admin:upOpen http://localhost:3000 and sign in with the bootstrap admin credentials.
The bootstrap account is created only when no operator account exists.
The Compose stack stores Postgres data in cms0_postgres_data and uploaded
assets, snapshots, and backups in cms0_admin_storage.
The Docker image uses the regular Next.js production runtime (next start).
It does not use standalone output.
Check container readiness with:
curl http://localhost:3000/api/healthStop the stack with:
pnpm docker:admin:downHealth checks
Use /api/health for infrastructure health checks. It returns 200 only when
the app can query Postgres with select 1, and returns 503 without leaking
connection details when the database is unavailable.
Use /api/content/health for authenticated runtime checks after creating an
API key.
Filesystem storage
Filesystem storage is useful for local development or a single-host deployment with persistent volumes.
Set:
CMS0_STORAGE_DRIVER=filesystem
CMS0_STORAGE_PATH=./storageMount the storage path to durable disk in production.
S3-compatible storage
Use S3-compatible storage when the app can move between hosts or containers.
Set:
CMS0_STORAGE_DRIVER=s3
CMS0_STORAGE_BUCKET="cms0"
CMS0_STORAGE_REGION="auto"
CMS0_STORAGE_ENDPOINT="https://s3.example.com"
CMS0_STORAGE_ACCESS_KEY_ID="..."
CMS0_STORAGE_SECRET_ACCESS_KEY="..."
CMS0_STORAGE_FORCE_PATH_STYLE=trueRuntime URL examples
Local
http://localhost:3000/api/contentBefore going live
- Use HTTPS for
CMS0_PUBLIC_APP_URLandBETTER_AUTH_URL. - Set
TRUSTED_ORIGINSto the exact origins that can access auth flows. - Store secrets in your host or platform secret manager.
- Keep
deploy/docker/admin.envuncommitted. - Create a backup and restore it in a non-production database.
- Create a scoped API key for CI.
Success check
Deployments are ready when sign-in, content reads, schema publishing, uploads, and backup restore all work through the public origin.