Two compose files

docker-compose.yml targets local dev — HTTP only, hot-reload bind mounts for admin and api. docker-compose.prod.yml builds the same services as multi-stage images, uses the hardened Caddyfile.prod and expects PUBLIC_URL on HTTPS.

bash
docker compose up -d
open http://localhost:8080
Dev — hot reload, HTTP only.
bash
docker compose -f docker-compose.prod.yml build
PUBLIC_URL=https://cms.example.com \
  docker compose -f docker-compose.prod.yml up -d
Prod — build images, point PUBLIC_URL at your domain.

Required env vars

These are checked at API boot — the process crashes loudly instead of running with bad defaults.

BETTER_AUTH_SECRETRequired

32+ random bytes. Signs session cookies and HMAC-hashes access tokens at rest. Rotating invalidates all tokens.

openssl rand -hex 32
PUBLIC_URLRequired

External HTTPS URL of the proxy. Used for webhook payloads, asset URLs, and OAuth redirect URIs.

https://cms.example.com
DATABASE_URLRequired

Postgres connection string.

postgres://cms:cms@postgres:5432/cms
S3_ENDPOINTRequired

S3-compatible endpoint the storage service talks to.

http://minio:9000
S3_ACCESS_KEY / S3_SECRET_KEYRequired

Credentials for the asset bucket — only the storage service reads these.

REDIS_URLOptional

If empty, the cache client is a silent no-op. If set, CDN responses and link maps are cached with short TTLs.

redis://redis:6379
OIDC_ISSUER_URLOptional

Presence toggles OIDC-only mode: disables email+password login and requires OIDC_CLIENT_ID + OIDC_CLIENT_SECRET.

https://auth.example.com/realms/main
OUTBOUND_ALLOW_LOOPBACKOptionalDefault: 0

SSRF guard escape hatch. Only set to 1 in tests that fetch against a local receiver — never gate by NODE_ENV.