Server
Environment Variables
Every server configuration knob, its default, and what it controls.
All configuration is read from the environment, lazily, at request time. Every value is optional - the defaults below are the running defaults.
Reference
| Variable | Default | Controls |
|---|---|---|
DATA_DIR | ./data locally, /app/data in Docker | Directory holding the SQLite file. |
ALLOWED_ORIGINS | * | Comma-separated CORS allowlist, or * for any origin. |
DASHBOARD_PASSWORD | (unset) | When set, the dashboard requires HTTP Basic auth (any username). |
INGEST_TOKEN | (unset) | When set, /api/ingest requires this token. |
RETENTION_DAYS | (unset / 0) | Delete sessions older than N days (swept hourly). Unset or 0 keeps everything. |
RATE_LIMIT_PER_MIN | 100 | Ingest requests allowed per minute per IP. |
MAX_PAYLOAD_BYTES | 5000000 | Max ingest body size in bytes; larger bodies get 413. |
Notes
ALLOWED_ORIGINS- use*for local testing. In production, list your exact origins, e.g.https://app.example.com,https://www.example.com.DASHBOARD_PASSWORD- protects only the dashboard. Ingestion,/api/health, and the SDK bundle stay open so recording keeps working. See Auth.INGEST_TOKEN- accepted in the request body (token) or as anAuthorization: Bearer <token>header. Body wins becausesendBeaconflushes cannot set headers.RETENTION_DAYS- invalid or<= 0values are treated as "keep forever".- Port - the server always listens on
3000inside the container. Map it to a different host port with Docker, e.g.-p 8080:3000.
Lazy by design
Config is read per call, not at import, so the running process always reflects its current environment.
A locked-down example
docker run -p 3000:3000 \
-v $(pwd)/data:/app/data \
-e DASHBOARD_PASSWORD=replace-with-a-password \
-e INGEST_TOKEN=$(openssl rand -hex 16) \
-e ALLOWED_ORIGINS=https://app.example.com \
-e RETENTION_DAYS=30 \
tinyreplay