All three services read the repo-root .env in development; under Compose the values come from the environment: blocks. Validation runs once at boot with zod — a malformed value fails the process immediately with the variable's name.
Secrets
| Variable | Used by | Required | Notes |
|---|
ENCRYPTION_KEY | api (admin), worker | yes | 64 hex chars (32 bytes). AES-256-GCM key for every stored credential. The public API works without it; the admin surface and the worker refuse to. |
HMAC_SECRET | api, worker | yes | ≥ 16 chars. Signs tracking ids. Changing it invalidates tracking for pushes already in flight. |
ADMIN_API_TOKEN | api, dashboard | dashboard only | ≥ 24 chars. Unset → /v1/admin answers 404. |
SESSION_SECRET | dashboard | yes | Signs the session cookie. |
ADMIN_PASSWORD_HASH | dashboard | yes | argon2id hash from bun run generate:admin-password, base64-encoded as printed. Only used while no members exist. |
Datastores
| Variable | Default | Notes |
|---|
MONGO_URL | mongodb://localhost:27017 | Set to mongodb://mongo:27017 by Compose. |
MONGO_DB_NAME | opennotification | |
REDIS_URL | redis://localhost:6379 | Must be reachable at API boot or the API exits 1. |
MONGO_CACHE_GB | 1 | WiredTiger cache; about half the RAM you can spare. Compose only. |
REDIS_MAXMEMORY | 512mb | Compose only; policy is always noeviction. |
API
| Variable | Default | Notes |
|---|
API_PORT | 3000 | |
API_PUBLIC_URL | http://localhost:3000 | The URL SDKs call. Informational (setup hints), not routing. |
CORS_ORIGINS | (none) | Comma-separated browser origins allowed on /v1/subscriptions and /v1/e. |
SUBSCRIBE_RATE_LIMIT | 10 | Per client IP per minute on subscribe/rotate — the public key is not a secret. |
SEND_RATE_LIMIT | 100 | Per API key per minute on /v1/notifications. |
EVENT_RATE_LIMIT | 600 | Per client IP per minute on tracking pings. |
EVENT_RETENTION_DAYS | 180 | TTL on the events time-series collection. Applied when the collection is created. |
API_KEY_CACHE_TTL | 60000 ms | How long a verified key skips bcrypt. |
Rate limits are per process. With N API replicas the effective ceiling is N × limit, which is fine for what they guard against (a runaway SDK loop, a scraper on subscribe).
Worker
| Variable | Default | Notes |
|---|
APNS_CONCURRENCY | 200 | Jobs in flight on the push-ios queue. One HTTP/2 connection multiplexes them. |
FCM_CONCURRENCY | 100 | push-android. |
WEBPUSH_CONCURRENCY | 100 | push-web. |
FANOUT_CONCURRENCY | 4 | Campaigns being fanned out at once. |
WEBHOOK_CONCURRENCY | 10 | Webhook batches in flight. |
PUSH_RATE_LIMIT_MAX / PUSH_RATE_LIMIT_DURATION_MS | 5000 / 1000 | BullMQ limiter per push queue. |
PUSH_MAX_ATTEMPTS | 3 | Retries for transient failures only (429, 503, UNAVAILABLE, INTERNAL). Permanent failures never retry. |
PUSH_BACKOFF_MS | 2000 | Exponential backoff base. |
INACTIVE_SUBSCRIPTION_DAYS | 90 | The maintenance sweep opts out subscriptions untouched this long. |
SHUTDOWN_TIMEOUT_MS | 30000 | How long SIGTERM waits for in-flight jobs. Keep Compose's stop_grace_period above it. |
Dashboard
| Variable | Default | Notes |
|---|
API_URL | — | Where the dashboard server reaches the API. Never exposed to the browser. Under Compose it is fixed to http://api:3000. |
DASHBOARD_PORT | 3001 | Loopback host port for the dashboard container. |
INSECURE_COOKIES | false | true drops the Secure flag for a plain-http localhost run. |
Dokploy / Traefik file
| Variable | Default | Notes |
|---|
API_DOMAIN | — | Required by docker-compose.dokploy.yml. |
DASHBOARD_DOMAIN | — | Required by docker-compose.dokploy.yml. |
DOCS_DOMAIN | — | Required by docker-compose.dokploy.yml. |
TRAEFIK_NETWORK | dokploy-network | The external Traefik network. |
TRAEFIK_CERT_RESOLVER | letsencrypt | |
API_IMAGE / WORKER_IMAGE / DASHBOARD_IMAGE | ghcr.io/aproder/opennotification-*:latest | Pull instead of build. |
LOG_LEVEL | info | |
Backups (--profile backup)
| Variable | Default | Notes |
|---|
BACKUP_INTERVAL_HOURS | 24 | Time between mongodump runs. |
BACKUP_KEEP_DAYS | 14 | Archives older than this are deleted after each successful run. |
BACKUP_DIR | mongo_backups volume | Set to a host path to write dumps there instead. |
VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT in .env are produced by generate:keys for convenience. They are per app, stored in the database when you configure web push for an app, and not read by the services from the environment.