Appearance
Environment Variable Reference
All environment variables are validated at startup using Zod. If a required variable is missing or invalid, the server refuses to start with a clear error message.
Configuration is loaded from a .env file (via dotenv) and then validated against the schema in src/env.ts.
Database
| Variable | Default | Required | Description |
|---|---|---|---|
DB_CLIENT | pg | No | Database driver: pg, mysql, mysql2, sqlite3, better-sqlite3 |
DB_HOST | localhost | No | Database host |
DB_PORT | 5432 | No | Database port |
DB_DATABASE | cms | No | Database name |
DB_USER | odp_user | No | Database user |
DB_PASSWORD | `` | No | Database password |
DB_SSL | false | No | Enable SSL connection |
DB_SSL_REJECT_UNAUTHORIZED | true | No | Reject DB server certs that fail verification (set false to allow self-signed) |
DB_SSL_CA | — | No | Path to a CA bundle (PEM) used to verify the DB server cert — e.g. AWS RDS global-bundle.pem |
DB_POOL_MIN | 0 | No | Min connection pool size |
DB_POOL_MAX | 10 | No | Max connection pool size |
DB_FILENAME | — | No | SQLite database file path (SQLite only) |
DB_LOG_QUERIES | false | No | Log all raw SQL queries to console (requires LOG_LEVEL=debug) |
Server
| Variable | Default | Required | Description |
|---|---|---|---|
HOST | 0.0.0.0 | No | Bind address |
PORT | 6688 | No | HTTP port |
PUBLIC_URL | http://localhost:6688 | No | Publicly accessible base URL |
TRUST_PROXY | 127.0.0.1/8,::1/128,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7 | No | Fastify trustProxy — an IP/CIDR list (comma-separated), a hop count (1), or true/false. The default trusts proxies by address (loopback + RFC1918 + ULA) instead of by hop count, so request.ip resolves to the real client IP regardless of how many internal proxy tiers sit in front of the API and whether traffic arrives via the BFF or a direct app call. With a public CDN as the outermost hop, append its ranges; to tighten, replace the list with the exact proxy subnet (e.g. 10.0.1.0/24). Never true in production |
SECRET | — | Yes | JWT signing secret (min 1 character) |
ACCESS_TOKEN_TTL | 15m | No | Access token lifetime (e.g., 15m, 1h) |
REFRESH_TOKEN_TTL | 7d | No | Refresh token lifetime (e.g., 7d, 30d) |
SESSION_COOKIE_ENABLED | true | No | Enable session cookie mode |
SESSION_COOKIE_NAME | odp_session_token | No | Cookie name for session tokens |
LOG_LEVEL | info | No | trace, debug, info, warn, error, fatal |
LOG_STYLE | pretty | No | pretty (colored output) or raw (JSON) |
LOG_FILE | — | No | Path to write logs to a file |
LOG_REQUESTS | true | No | Log each HTTP request |
MAX_PAYLOAD_SIZE | 1mb | No | Maximum request body size |
ROOT_REDIRECT | /server/info | No | Where / redirects to |
NODE_ENV | development | No | development, production, test |
CORS
| Variable | Default | Description |
|---|---|---|
CORS_ENABLED | true | Enable CORS headers |
CORS_ORIGIN | * | Allowed origins (comma-separated or *) |
CORS_METHODS | GET,POST,PATCH,DELETE | Allowed HTTP methods |
CORS_ALLOWED_HEADERS | Content-Type,Authorization | Allowed request headers |
CORS_EXPOSED_HEADERS | Content-Range | Exposed response headers |
CORS_CREDENTIALS | true | Allow credentials |
CORS_MAX_AGE | 18000 | Preflight cache duration (seconds) |
Workflow
| Variable | Default | Description |
|---|---|---|
WORKFLOW_STRICT_NODES | false | When true, activating a workflow with an unknown node/action type (or a failing validateConfig) is rejected. When false, unknown nodes are allowed and park to waiting at runtime |
Cache
| Variable | Default | Description |
|---|---|---|
CACHE_ENABLED | false | Enable response caching |
CACHE_TTL | 5m | Cache entry lifetime |
CACHE_STORE | memory | Backend: memory or redis |
CACHE_AUTO_PURGE | false | Auto-invalidate on mutations |
CACHE_SCHEMA | true | Cache database schema |
CACHE_SYSTEM_TTL | 10m | System cache TTL |
CACHE_NAMESPACE | odp | Cache key prefix |
CACHE_STATUS_HEADER | x-cache-status | Response header for cache status |
CACHE_VALUE_MAX_SIZE | 1mb | Maximum cacheable response size |
Redis
| Variable | Default | Description |
|---|---|---|
REDIS_ENABLED | false | Use Redis for cache and sessions |
REDIS_HOST | 127.0.0.1 | Redis host |
REDIS_PORT | 6379 | Redis port |
REDIS_PASSWORD | `` | Redis password |
REDIS_DB | 0 | Redis database index |
REDIS_TLS | false | Enable TLS (managed Redis with in-transit encryption / rediss://) |
REDIS_TLS_REJECT_UNAUTHORIZED | true | Reject Redis server certs that fail verification (set false to allow self-signed) |
REDIS_TLS_CA | — | Path to a CA bundle (PEM) to verify the Redis server cert (self-signed / private CA) |
Storage & Files
| Variable | Default | Description |
|---|---|---|
STORAGE_LOCAL_ROOT | ./uploads | Local file storage directory |
FILES_MAX_UPLOAD_SIZE | 10mb | Maximum upload size per file |
FILES_MIME_TYPE_ALLOW_LIST | * | Allowed MIME types (* = all, or comma-separated list) |
FILES_MAX_UPLOAD_CONCURRENCY | 5 | Max simultaneous file uploads |
Image Transformation (Assets)
| Variable | Default | Description |
|---|---|---|
ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION | 6000 | Max output width or height (pixels) |
ASSETS_TRANSFORM_MAX_CONCURRENT | 25 | Max concurrent image transforms |
ASSETS_TRANSFORM_TIMEOUT | 30 | Transform timeout (seconds) |
ASSETS_CACHE_TTL | 30m | Transformed asset cache TTL |
ASSETS_INVALID_IMAGE_SENSITIVITY_LEVEL | warning | How to handle corrupt images: none, truncated, error, warning |
Email
| Variable | Default | Description |
|---|---|---|
EMAIL_FROM | no-reply@example.com | Default sender address |
EMAIL_TRANSPORT | smtp | Transport: smtp, ses, sendmail |
EMAIL_SMTP_HOST | localhost | SMTP server host |
EMAIL_SMTP_PORT | 587 | SMTP port |
EMAIL_SMTP_USER | `` | SMTP username |
EMAIL_SMTP_PASSWORD | `` | SMTP password |
EMAIL_SMTP_SECURE | false | Enable TLS/SSL |
Rate Limiting
| Variable | Default | Description |
|---|---|---|
RATE_LIMITER_ENABLED | true | Master toggle for every limiter (global, per-IP, per-principal, login, email) |
RATE_LIMITER_STORE | memory | Store: memory or redis |
RATE_LIMITER_POINTS | 50 | Anonymous per-IP requests per window (also charged to requests whose token matches no auth strategy) |
RATE_LIMITER_DURATION | 1 | Per-IP window duration (seconds) |
RATE_LIMITER_GLOBAL_ENABLED | true | Enable global (all-IP) rate limit |
RATE_LIMITER_GLOBAL_POINTS | 1000 | Global requests per window |
RATE_LIMITER_GLOBAL_DURATION | 1 | Global window duration (seconds) |
RATE_LIMITER_USER_POINTS | 300 | Per-user requests per window (roles without service_account) |
RATE_LIMITER_USER_DURATION | 60 | Per-user window duration (seconds) |
RATE_LIMITER_SERVICE_POINTS | 10000 | Per-service-account requests per window (roles flagged service_account, e.g. the BFF appApi token — one bucket per service user) |
RATE_LIMITER_SERVICE_DURATION | 60 | Service-account window duration (seconds) |
RATE_LIMITER_LOGIN_POINTS | 10 | POST /auth/login attempts per client IP per window |
RATE_LIMITER_LOGIN_DURATION | 60 | Login window duration (seconds) |
RATE_LIMITER_ASSETS_POINTS | 300 | Anonymous /assets/* requests per IP per window (media Range streaming) |
RATE_LIMITER_ASSETS_DURATION | 1 | Assets window duration (seconds) |
Authentication Throttling
| Variable | Default | Description |
|---|---|---|
LOGIN_THROTTLE_DELAY | 1s | Initial delay after failed login |
LOGIN_THROTTLE_MAX_DELAY | 30s | Maximum backoff delay |
WebAuthn / Passkey
| Variable | Default | Description |
|---|---|---|
WEBAUTHN_RP_ID | — | Relying Party ID (domain passkeys bind to). Must never change once credentials are registered. Derives from PUBLIC_URL when unset — set explicitly in split deploys |
WEBAUTHN_RP_NAME | ODP | Human-readable Relying Party name shown during registration |
WEBAUTHN_ORIGIN | — | Comma-separated browser origins allowed to complete ceremonies (e.g. https://admin.example.com) |
Security Events
| Variable | Default | Description |
|---|---|---|
SECURITY_EVENTS_ENABLED | true | Record security events (login attempts, permission failures, etc.) |
SSRF Protection
| Variable | Default | Description |
|---|---|---|
SSRF_PROTECTION_ENABLED | true | Block outbound requests to private/internal addresses |
SSRF_ALLOWED_HOSTS | `` | Comma-separated hosts exempted from SSRF protection |
Audit Hash Chain
| Variable | Default | Description |
|---|---|---|
AUDIT_HASH_ENABLED | false | Enable tamper-evident hash chaining for audit records |
AUDIT_HMAC_KEY | `` | HMAC key used to sign the audit hash chain |
AUDIT_VERIFY_SCHEDULE | 0 * * * * | Cron schedule for the audit chain verification job |
AI Engine
| Variable | Default | Description |
|---|---|---|
ASSISTANT_RATE_LIMIT_PER_MIN | 20 | Rate limit for the public/anonymous assistant chat (per minute) |
ASSISTANT_INGEST_COLLECTIONS | `` | Comma-separated collections to live-sync to the knowledge backend on item create/update/delete; empty disables the listeners |
ASSISTANT_LLM_PROVIDER | openai-compatible | LLM provider fallback when an ai_integrations row / app config leaves it blank |
ASSISTANT_LLM_BASE_URL | http://localhost:20128/v1 | LLM API base URL (OpenAI-compatible) |
ASSISTANT_LLM_MODEL | gpt-4o-mini | Default LLM model |
ASSISTANT_LLM_API_KEY | — | LLM API key |
KAOS_BASE_URL | http://localhost:7071 | KAOS RAG store base URL |
KAOS_API_KEY | — | KAOS API key |
KAOS_IDENTITY_ID | odp | KAOS service identity ID |
KAOS_IDENTITY_KIND | service | KAOS identity kind |
KAOS_IDENTITY_SCOPES | knowledge:jobs:write,knowledge:read | KAOS identity scopes (comma-separated) |
AI_INGEST_MAX_CONTENT_BYTES | 15728640 | Max bytes per ingested document (sits just under KAOS's 16MB body limit) |
AI_MOCK_RETRIEVAL | false | Dev-only synthetic retrieval so a RAG flow completes without real KAOS data. Hard-disabled in production |
AI_HISTORY_TOKEN_BUDGET | 3000 | Conversation-memory history budget (tokens); crossing 50% triggers a rolling summary |
AI_ENABLE_CODE_NODE | false | Gate for the code flow node (Function() eval — NOT a sandbox) |
Configuration Drift Detection
| Variable | Default | Description |
|---|---|---|
DRIFT_DETECTION_ENABLED | false | Enable configuration drift detection |
DRIFT_CHECK_SCHEDULE | */5 * * * * | Cron schedule for the drift check job |
WebSocket
| Variable | Default | Description |
|---|---|---|
WEBSOCKETS_ENABLED | false | Enable WebSocket support (opt-in — set true to turn on; the /websocket route is not registered when disabled) |
WEBSOCKETS_HEARTBEAT_ENABLED | true | Enable ping/pong heartbeat (only applies when WEBSOCKETS_ENABLED=true) |
WEBSOCKETS_HEARTBEAT_PERIOD | 30 | Heartbeat interval (seconds) |
WEBSOCKETS_REST_ENABLED | true | Enable REST-over-WebSocket |
WEBSOCKETS_REST_AUTH | handshake | Auth mode: public, handshake, strict |
WEBSOCKETS_GRAPHQL_ENABLED | true | Enable GraphQL over WebSocket |
WEBSOCKETS_GRAPHQL_AUTH | handshake | Auth mode: public, handshake, strict |
Extensions
| Variable | Default | Description |
|---|---|---|
EXTENSIONS_PATH | ./extensions | Directory to load extensions from |
EXTENSIONS_MUST_LOAD | `` | Comma-separated list of extensions that must load (server fails if missing) |
TUS Resumable Upload
| Variable | Default | Description |
|---|---|---|
TUS_UPLOAD_EXPIRATION | 10m | Expiry for incomplete TUS uploads |
TUS_CLEANUP_SCHEDULE | 0 */6 * * * | Cron schedule for TUS cleanup job |
Miscellaneous
| Variable | Default | Description |
|---|---|---|
MIGRATIONS_PATH | `` | Path to custom migration files |
PRESSURE_ENABLED | true | Enable server load shedding |
METRICS_ENABLED | false | Expose Prometheus metrics endpoint |
METRICS_NAME_PREFIX | odp_ | Prometheus metric name prefix |
USER_INVITE_TOKEN_TTL | 7d | Invite token lifetime |
EMAIL_VERIFICATION_TOKEN_TTL | 7d | Email verification token lifetime |
REGISTER_STALL_TIME | 750 | Minimum response time (ms) for registration to mask user enumeration |
USER_REGISTER_URL_ALLOW_LIST | `` | Comma-separated URLs allowed as registration verification redirect targets |
QUERYSTRING_MAX_PARSE_DEPTH | 10 | Max nesting depth for query string parsing |
Example .env
env
# Required
SECRET=your-super-secret-key-change-in-production
# Database
DB_CLIENT=pg
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=odp
DB_USER=odp_user
DB_PASSWORD=odp_password
# Server
PORT=6688
PUBLIC_URL=https://api.example.com
LOG_LEVEL=info
LOG_STYLE=raw
# Cache (Redis)
CACHE_ENABLED=true
CACHE_STORE=redis
REDIS_ENABLED=true
REDIS_HOST=redis
REDIS_PORT=6379
# Email
EMAIL_FROM=no-reply@example.com
EMAIL_TRANSPORT=smtp
EMAIL_SMTP_HOST=smtp.example.com
EMAIL_SMTP_PORT=587
EMAIL_SMTP_USER=smtp-user
EMAIL_SMTP_PASSWORD=smtp-password
EMAIL_SMTP_SECURE=true
# Storage
STORAGE_LOCAL_ROOT=/data/uploads
FILES_MAX_UPLOAD_SIZE=50mb
# Security
RATE_LIMITER_ENABLED=true
SESSION_COOKIE_ENABLED=true