Production environment variables
Look up the production image, origin, domain, secret, storage, authentication, email, proxy, worker, and lifecycle settings Nibleaf consumes.
- 8 min read
- Updated Aug 31, 2026
Start from .env.production.example shipped with the release. This page explains
the setting groups, but the template and Compose file determine what that release
actually consumes. Never commit a populated .env.
Image and public application
| Variable | Required | Default | Purpose |
|---|---|---|---|
NIBLEAF_VERSION | Yes in practice | v0.1.2 in the current template | GHCR release tag. Pin an existing release; avoid floating tags. The manual Docker workflow rejects overwriting an existing v* release tag, making published version tags immutable by repository policy. |
APP_URL | Yes | None | Browser-facing dashboard and normal authentication origin, without a trailing slash. |
ADMIN_URL | Only when admin is exposed | http://localhost:4315 | Public origin for the optional operator console. |
NIBLEAF_BIND | No | 127.0.0.1 | Host interface for the app port. Keep loopback when the proxy is local. |
APP_PORT | No | 4310 | Host port mapped to the app container. |
BETTER_AUTH_URL | No | APP_URL | Explicit authentication base override. |
TRUSTED_ORIGINS | No | APP_URL | Comma-separated authentication origins. Include both APP_URL and ADMIN_URL when the admin console is exposed so native support impersonation can begin there. |
CORS_ALLOWED_ORIGINS | No | APP_URL | Comma-separated API origins. Include both APP_URL and ADMIN_URL when the admin console calls the shared API directly. |
PINO_LOG_LEVEL | No | info in production | Minimum structured log level. |
PINO_LOG_FILE | No | Empty | Optional JSONL disk destination in addition to terminal output. Mount the parent directory to persist container logs. |
Published documentation domains
| Variable | Required | Default | Purpose |
|---|---|---|---|
SITE_BASE_DOMAIN | No | Empty | Enables <project>.<base-domain> publishing; requires wildcard DNS and TLS. |
CUSTOM_DOMAIN_CNAME_TARGET | No | SITE_BASE_DOMAIN | Operator-controlled CNAME target shown to custom-domain owners. |
CUSTOM_DOMAIN_PROVIDER | No | ingress | Custom-domain automation strategy supported by the release. |
CLOUDFLARE_SAAS_ZONE_ID | For Cloudflare for SaaS | Empty | Zone identifier for managed custom hostnames. |
CLOUDFLARE_SAAS_API_TOKEN | For Cloudflare for SaaS | Empty | Narrow token for custom-hostname operations. |
CLOUDFLARE_SAAS_WORKER_SCRIPT | No | nibleaf-custom-domain-edge | Edge worker script name. |
CUSTOM_DOMAIN_EDGE_SECRET | For the managed edge path | Empty | Shared edge-to-origin authentication secret. |
Required secrets
| Variable | Required | Purpose |
|---|---|---|
BETTER_AUTH_SECRET | Yes | Signs authentication state. Generate independently with openssl rand -hex 32. |
POSTGRES_PASSWORD | Yes | Password for the bundled Nibleaf database user. |
INTERNAL_API_SECRET | Strongly recommended | Authenticates trusted app-to-API hints used for accurate rate limiting. |
Object storage
| Variable | Required | Default | Purpose |
|---|---|---|---|
STORAGE_PROVIDER | No | maxio | Provider behavior such as maxio, r2, or another supported S3-compatible mode. |
STORAGE_ENDPOINT | For external storage | http://maxio:9000 | Server-to-storage endpoint. |
STORAGE_PUBLIC_ENDPOINT | Yes | None | Browser-reachable origin used for presigned requests. |
STORAGE_ACCESS_KEY_ID | Yes | None | Bucket access identifier. |
STORAGE_SECRET_ACCESS_KEY | Yes | None | Bucket secret. Generate independently. |
STORAGE_BUCKET | No | nibleaf | Asset and export bucket. |
STORAGE_REGION | No | auto | S3 region value. |
STORAGE_FORCE_PATH_STYLE | No | true | Use path-style bucket URLs; AWS S3 commonly uses false. |
STORAGE_PUBLIC_URL | Recommended | Empty | Public asset base, often a bucket or CDN URL. |
STORAGE_CORS_ALLOWED_ORIGINS | No | APP_URL | Browser origins allowed to upload directly. |
STORAGE_AUTO_CORS | No | true | Let Nibleaf configure supported bucket CORS automatically. |
MAXIO_SECURE_COOKIES | No | true | Require HTTPS for bundled maxio console cookies. |
Authentication and email
| Variable | Required | Default | Purpose |
|---|---|---|---|
DISABLE_SIGNUP | No | false | Close new registration while preserving existing sign-in. |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | For Google sign-in | Empty | OAuth client credentials. |
EMAIL_FROM | For delivery | nibleaf@localhost | Provider-verified sender. |
POSTMARK_API_KEY | For Postmark | Empty | Server token; preferred when set. |
POSTMARK_MESSAGE_STREAM | No | Empty | Postmark stream. |
SMTP_URL | For SMTP fallback | Empty | Full SMTP connection URL. Protect it as a secret. |
EMAIL_DELIVERY_REQUIRED | No | true in production | Return an unhealthy worker status and fail queued email jobs when no provider is configured. |
Google OAuth
Create a Web application OAuth client in Google Cloud, then configure:
- Authorized JavaScript origin: the exact
APP_URL, such ashttps://nibleaf.example.com. - Authorized redirect URI:
${APP_URL}/api/auth/callback/google. GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRET: the matching client credentials.
Both variables must be present and non-blank after trimming surrounding whitespace. Nibleaf hides the Google button when either value is missing, so a partially configured provider cannot send readers into a broken sign-in flow. Restart the application after changing these server-side values.
Optional product and traffic controls
| Variable | Required | Default | Purpose |
|---|---|---|---|
OPENROUTER_API_KEY | Only for AI features | Empty | Enables editor drafting, embeddings, and optional grounded answers through OpenRouter. |
AI_DRAFT_MODEL | No | openai/gpt-4o-mini | OpenRouter model used for explicit editor writing actions. |
AI_DAILY_LIMIT | No | Application default | Per-workspace daily AI request limit; 0 disables drafting. |
MARKETING_GTM_ID | No | Empty | Preferred GTM container ID for public instance marketing pages. It is public metadata and loads only after explicit visitor consent. Configure the GA4 Google tag in GTM with automatic pageviews disabled; Nibleaf pushes one manual page_view per accepted route plus allowlisted events. This variable takes precedence over MARKETING_GA4_ID. |
MARKETING_GA4_ID | No | Empty | Legacy direct-GA4 fallback for existing self-hosters. It loads only after explicit consent and is ignored when MARKETING_GTM_ID is configured. |
RATE_LIMIT_PUBLIC_PER_MIN | No | Application default | Public site-serving requests per address per minute. |
TRUSTED_PROXY_HOPS | No | 0 | Count of trusted public proxies that append to the forwarding chain. |
GTM container contract
When MARKETING_GTM_ID is enabled, configure one Google tag in GTM with automatic pageviews disabled and its normal page initialization trigger. Then configure one GA4 Event tag triggered by the Custom Event nibleaf_marketing_event, with its GA event name read from the Data Layer Variable event_name. Nibleaf uses that stable envelope for the allowlisted names page_view, sign_up, cta_clicked, free_tool_started, free_tool_completed, and free_tool_cta_clicked, so six separate GTM Event tags are not required. Map only the documented safe event variables. Do not add variables that read form fields, account data, document content, or arbitrary DOM values into analytics. Keep consent checks enabled on both tags. Nibleaf loads the container only after acceptance, pushes one manual page_view for the accepted route and each later SPA navigation, and stops event delivery when consent is withdrawn.
Operator and lifecycle settings
Model Context Protocol
| Variable | Required | Default | Purpose |
|---|---|---|---|
MCP_ENABLED | No | false | Enables the authenticated project-bound /api/mcp endpoint. Keep it disabled until proxy and key-lifecycle checks pass. |
MCP_ALLOWED_HOSTS | No | Empty | Comma-separated additional public hostnames. API_URL and APP_URL hosts are already allowed. Schemes, paths, wildcard values, and ambiguous forwarded chains are rejected. |
MCP_RATE_LIMIT_PER_MIN | No | 120 | Per-key requests accepted by one API process per minute. Add an ingress/shared-edge limit for deployment-wide enforcement. |
See secure MCP client setup and operations before enabling the endpoint. API-key secrets are project-admin managed, displayed once, and must not be stored in environment examples or repository configuration.
Export workers
| Variable | Required | Default | Purpose |
|---|---|---|---|
EXPORT_CHROMIUM_PATH | For PDF outside the official image | /usr/bin/chromium-browser | Chromium-compatible executable used for PDF rendering. |
EXPORT_CONCURRENCY | No | 2 | Export jobs processed by one worker; accepted range is 1–8. |
EXPORT_MAX_ACTIVE_PER_PROJECT | No | 3 | Concurrent project runs accepted by the API. |
EXPORT_MAX_DAILY_PER_PROJECT | No | 20 | Per-project daily run limit. |
EXPORT_MAX_PAGES | No | 5000 | Maximum pages copied into one immutable export snapshot. |
EXPORT_MAX_SNAPSHOT_BYTES | No | 52428800 | Maximum serialized snapshot bytes. |
EXPORT_MAX_ASSET_BYTES | No | 262144000 | Maximum referenced asset bytes copied into an artifact. |
EXPORT_MANUAL_RETENTION_DAYS | No | 7 | Retention for one-time export runs. |
EXPORT_DOWNLOAD_TTL_SECONDS | No | 300 | Presigned download lifetime; accepted range is 30–900 seconds. |
The official image includes Chromium. If WORKER_QUEUES is a comma-separated
allowlist, include export; an empty value enables every queue.
Bidirectional Git
| Variable | Required | Default | Purpose |
|---|---|---|---|
GIT_CREDENTIAL_ENCRYPTION_KEY | To enable two-way Git | None | Exactly 32 bytes encoded as base64; encrypts provider credentials and webhook secrets. |
GIT_WORKER_SECRET | To enable two-way Git | None | At least 32 characters shared by API and worker for opaque callbacks. |
GIT_CONCURRENCY | No | 2 | Git operations processed by one worker. |
Generate the two secrets independently. Do not rotate the encryption key in
place: existing ciphertext cannot be decrypted with a new key. Follow the
Git authoring rotation procedure. If WORKER_QUEUES is set,
include git.
Project integrations
| Variable | Required | Default | Purpose |
|---|---|---|---|
INTEGRATION_CREDENTIAL_ENCRYPTION_KEY | To create project webhook connections | None | Independent 32-byte base64 key for versioned AES-256-GCM credential envelopes and purpose-separated HMAC digests. Do not reuse the Git key. |
INTEGRATION_CREDENTIAL_PREVIOUS_KEYS | During key rotation only | Empty | Comma-separated prior integration keys accepted for decrypt and digest lookup during a bounded dual-read window. |
INTEGRATION_CONFIRMATION_TTL_SECONDS | No | 300 | Lifetime of a one-time, revision-bound integration delete confirmation; accepted range is 60–1800 seconds. |
INTEGRATION_CONFIRMATION_RETENTION_HOURS | No | 24 | Retention for consumed confirmation tombstones before bounded cleanup; accepted range is 1–168 hours. Expired unconsumed challenges are removed on the next project challenge. |
INTEGRATION_IDEMPOTENCY_TTL_HOURS | No | 24 | Retention for secret-free durable mutation replays; accepted range is 1–168 hours. |
Follow the rotation and rollback procedure in Integrations engine.
General operator settings
| Variable | Required | Default | Purpose |
|---|---|---|---|
WORKBENCH_USER, WORKBENCH_PASS | Recommended | Empty | Basic authentication for worker operations at port 4312. |
NIBLEAF_RUN_SEED | No | false | Seed demo content during migration; keep false in production. |
SERVER_SHUTDOWN_TIMEOUT_MS | No | 25000 | API drain timeout in milliseconds. |
NIBLEAF_SHUTDOWN_GRACE | No | 30s | Container stop grace; keep longer than the server timeout. |
After changing origins, secrets, or storage, recreate the affected services and repeat the workflow checks in production readiness.