Self-Hosted Environment Reference
Start with the product repository's .env.example. It is the smallest supported Compose configuration. Use .env.full.example when you need an optional setting; this page explains which settings belong together and when to change them.
After editing .env, validate interpolation without printing the resolved configuration:
docker compose config --quiet
Do not commit .env, paste it into support requests, or expose the output of docker compose config: the resolved output contains secrets.
Required production values
Generate each secret independently with openssl rand -hex 32. Keep the old values through every upgrade and store a recovery copy outside the server.
| Variable | Production value | Why it matters |
|---|---|---|
POSTGRES_PASSWORD | Unique random password | Protects the bundled PostgreSQL database. |
WEB_ORIGIN | Public HTTPS app origin, such as https://kanera.example.com | Used for browser links, CORS, and authentication flows. Do not include a trailing slash. |
COOKIE_DOMAIN | App hostname, such as kanera.example.com | Scope for the refresh cookie. |
COOKIE_SECURE | true | Required when the app is served over HTTPS. |
JWT_SECRET | Stable random secret, at least 16 characters | Signs user sessions. Rotation signs everyone out. |
MCP_INTERNAL_SECRET | Stable random secret, at least 32 characters and different from JWT_SECRET | Lets the MCP and public API services exchange audience-bound OAuth credentials without forwarding an MCP token into /api/v1. Both services must always use the same value. |
MFA_ENCRYPTION_KEY | Stable random secret, at least 32 characters | Protects authenticator enrollment and challenges. Rotation invalidates enrolled authenticators. |
MEDIA_SIGNING_SECRET | Stable random secret, at least 32 characters | Signs private media URLs. Rotation invalidates existing signed URLs. |
SECRETS_ENCRYPTION_KEY | Stable random secret, at least 32 characters and different from JWT_SECRET | Encrypts SMTP, storage, webhook, and integration credentials stored in the database. |
KANERA_DEPLOYMENT_MODE defaults to self_hosted. Hosted mode and its Stripe, PostHog, plan-limit, and licensing variables are for Kanera-operated deployments and should remain unset on a self-hosted installation.
Compose supplies NODE_ENV=production and ANALYTICS_ENABLED=false. Leave ANALYTICS_PROVIDER, POSTHOG_PROJECT_KEY, POSTHOG_API_HOST, every STRIPE_*, HOSTED_*, and hosted-mode token unset. These settings do not turn a self-hosted installation into Kanera Cloud.
Public URLs and services
| Variable | Default | Set it when |
|---|---|---|
KANERA_ENVIRONMENT | production in production Compose | You need a different API-key prefix for development, test, or staging. |
API_PUBLIC_URL | WEB_ORIGIN | The browser-facing app API uses a different public origin. |
KANERA_PUBLIC_API_URL | http://public-api:3001 in Compose | MCP or another internal process reaches the public API somewhere other than its Compose service address. This is an internal URL, not the browser-facing API origin. |
PUBLIC_API_OAUTH_ISSUER | http://localhost:3001 | You expose OAuth or the public API remotely; use its browser-reachable HTTPS origin. |
MCP_SERVER_PUBLIC_URL | http://localhost:3002/mcp | You expose remote MCP; use the complete public /mcp URL. |
MCP_PUBLIC_URL | Derived from MCP_SERVER_PUBLIC_URL by Compose | You run the MCP service outside the bundled Compose wiring. |
OAUTH_ISSUER_URL | Derived from PUBLIC_API_OAUTH_ISSUER by Compose | You run MCP outside the bundled Compose wiring. |
API_PORT, PUBLIC_API_PORT, WORKER_PORT, MCP_PORT | 3000, 3001, 3003, 3002 | You run services outside Compose or need different internal listener ports. |
API_TRUST_PROXY, PUBLIC_API_TRUST_PROXY, MCP_TRUST_PROXY | false, false, true | A trusted reverse proxy supplies the real client IP. Never trust forwarded headers from arbitrary clients. |
The browser connects to web; do not publish the main api service. See Services and Routing before exposing the public API or MCP.
Accounts and authentication
| Variable | Default | Purpose |
|---|---|---|
SIGNUPS_ENABLED | true | Set false to stop public signup and new organisation creation. Existing organisation invitation links still work. |
EMAIL_VERIFICATION_ENABLED | false | Require a six-digit email check for signup, invite signup, and email changes. Enable only after SMTP works. |
AUTH_RATE_LIMIT_ENABLED | true | Enable the per-IP brute-force throttle on unauthenticated auth routes. |
AUTH_RATE_LIMIT_MAX / AUTH_RATE_LIMIT_WINDOW_MS | 10 / 60000 | Allowed auth attempts per window. |
JWT_ACCESS_TTL / JWT_REFRESH_TTL_DAYS | 5m / 10 | Access-token and refresh-session lifetimes. |
CLOUDFLARE_TURNSTILE_SITE_KEY / CLOUDFLARE_TURNSTILE_SECRET_KEY | Unset | Hosted signup protection. Leave unset for normal self-hosting. |
Users configure their own authenticator under Profile settings -> Profile. Organisation admins can require it under Profile settings -> Organisation. See Profile and Security.
Public API and MCP limits
| Variable | Default | Purpose |
|---|---|---|
PUBLIC_API_RATE_LIMIT_ENABLED | true | Enables public API and MCP request limits. |
PUBLIC_API_IP_RATE_LIMIT_PER_MINUTE | 60 | Limit for unauthenticated discovery and invalid bearer formats. |
PUBLIC_API_FAILED_KEY_RATE_LIMIT_PER_MINUTE | 10 | Per-IP limit for failed kanera_* key attempts. |
PUBLIC_API_KEY_RATE_LIMIT_PER_MINUTE | 400 | Per-key request limit after successful authentication. |
PUBLIC_API_UPLOAD_RATE_LIMIT_PER_MINUTE | 30 | Per-key attachment-upload limit. |
PUBLIC_API_RATE_LIMIT_WINDOW_MS | 60000 | Rate-limit window shared by the public API and MCP. |
MCP_BODY_MAX_BYTES | 1048576 | Maximum MCP request body. |
MCP_REQUEST_TIMEOUT_MS | 30000 | Maximum time for an MCP request. |
MCP_HEADERS_TIMEOUT_MS / MCP_KEEP_ALIVE_TIMEOUT_MS | 10000 / 5000 | MCP HTTP connection timeouts. |
KANERA_API_KEY | Unset | Required only by the local stdio MCP bridge, not the HTTP MCP service. |
Database, cache, and capacity
Compose supplies DATABASE_URL and REDIS_URL for the bundled PostgreSQL and Valkey services. Set them yourself only when running a process outside that network or using managed services.
| Variable | Compose default | Purpose |
|---|---|---|
DATABASE_SSL | false | Enable only when an external database requires SSL. |
POSTGRES_BIND_PORT | 5433 | Loopback-only host port for bundled PostgreSQL. |
API_REPLICAS / PUBLIC_API_REPLICAS / MCP_REPLICAS | 2 / 1 / 1 | Service process counts. Keep the worker at exactly one. |
PG_POOL_MAX / WORKER_PG_POOL_MAX | 20 / 5 | Maximum database connections per app process and for the worker. |
PG_IDLE_TIMEOUT_MS / PG_CONNECTION_TIMEOUT_MS | 30000 / 5000 | Pool idle and acquisition timeouts. |
PG_STATEMENT_TIMEOUT_MS / SLOW_QUERY_LOG_MS | 30000 / 250 | Database query timeout and slow-query logging threshold. |
NODE_OPTIONS | Unset | Optional Node flags, such as a heap limit. |
UV_THREADPOOL_SIZE | 16 | Native worker threads used by hashing, crypto, and image processing. |
REALTIME_WEBSOCKET_COMPRESSION_ENABLED | true | Compress larger Socket.IO WebSocket messages. |
REALTIME_WEBSOCKET_COMPRESSION_THRESHOLD_BYTES | 1024 | Minimum realtime frame size to compress. |
REALTIME_OUTBOX_POLL_MS | 500 in Compose | Worker fallback polling interval for realtime and webhook outbox rows. |
USER_DISPLAY_CACHE_TTL_MS | 60000 | In-process cache lifetime for display-name lookups. |
Raise replicas and pools together only after measuring capacity. See Updates and Scaling.
Files and object storage
| Variable | Default | Purpose |
|---|---|---|
UPLOADS_DIR | /data/uploads in Compose | Local upload directory. Bundled Compose fixes this path and persists it in kanera_uploads; change the volume mapping rather than .env. |
ATTACHMENT_MAX_BYTES | 104857600 | Maximum uploaded attachment size in bytes. |
S3_REGION / S3_BUCKET | Unset | Start deployment-wide S3-compatible storage when supplied with credentials. |
S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY | Unset | Credentials for the upload bucket. Both are required for environment-based S3. |
S3_ENDPOINT | Unset | Custom endpoint for MinIO, R2, or another S3-compatible provider. Setting it enables path-style requests. |
S3_PUBLIC_URL_PREFIX | Unset | Optional public or CDN prefix for stored objects. |
Environment storage takes precedence over organisation storage configured in the app. See Storage and Backups before switching an installation that already has uploads.
Email, notifications, and integrations
| Variable | Default | Purpose |
|---|---|---|
SMTP_HOST, SMTP_USER, SMTP_PASSWORD, SMTP_FROM_EMAIL | Unset | Default outbound SMTP connection and sender. |
SMTP_PORT / SMTP_SECURITY | 587 / starttls | SMTP port and transport: starttls, tls, or none. |
SMTP_FROM_NAME | Kanera | Sender display name. |
SMTP_IDENTITY_DOMAIN | Sender email domain | EHLO and Message-ID domain. |
INTERNAL_NOTIFICATION_EMAILS | Unset | Comma-separated recipients for plain-text signup and invite alerts. Requires environment SMTP. |
KANERA_ALLOW_PRIVATE_NOTIFICATION_DESTINATIONS | false | Allow personal ntfy, Gotify, and webhook receivers on HTTP or private networks. Reserved and metadata addresses remain blocked. |
ALERT_WEBHOOK_URL | Unset | Slack-compatible destination for readiness, runtime, and Grafana alerts. |
OPS_ALERTS_ENABLED / OPS_ALERT_THROTTLE_MS | true / 300000 | Enable and deduplicate operational alerts. Nothing is sent without a destination. |
SLOW_REQUEST_LOG_MS | 2500 | Threshold for a slow-request warning in logs. |
GITHUB_APP_ID, GITHUB_APP_SLUG, GITHUB_APP_PRIVATE_KEY | Unset | Optional deployment-defined GitHub App for private link previews. Self-hosted admins can instead use the in-app manifest flow. |
TRELLO_API_KEY | Unset | Lets Trello imports authenticate and copy attachments. |
See Email and Notifications and Integrations and APIs.
Backups and retention
| Variable | Default | Purpose |
|---|---|---|
DB_BACKUPS_ENABLED | false | Enable scheduled encrypted PostgreSQL backups to S3-compatible storage. |
DB_BACKUP_ENCRYPTION_PASSPHRASE | Unset | Required when backups are enabled and required again to restore them. |
DB_BACKUP_TIMES_UTC | 00:15,12:15,16:45 | Comma-separated daily UTC backup times. |
DB_BACKUP_RETENTION_DAYS | 14 | Successful backup retention window. |
DB_BACKUP_S3_PREFIX | backups/postgres | Object prefix for database dumps. |
DB_BACKUP_S3_BUCKET, DB_BACKUP_S3_REGION, DB_BACKUP_S3_ENDPOINT | Upload-storage values | Optional separate backup destination. |
DB_BACKUP_S3_ACCESS_KEY_ID, DB_BACKUP_S3_SECRET_ACCESS_KEY | Upload-storage values | Optional separate backup credentials. |
ACTIVITY_EVENT_RETENTION_DAYS | 730 | Activity history retention. |
ADMIN_AUDIT_LOG_RETENTION_DAYS | 1095 | Management audit-log retention. |
NOTIFICATION_READ_RETENTION_DAYS / NOTIFICATION_MAX_RETENTION_DAYS | 90 / 365 | Read-notification and absolute notification retention. |
AUTH_TOKEN_RETENTION_DAYS | 30 | Grace period before expired or consumed auth artifacts are purged. |
REALTIME_OUTBOX_RETENTION_DAYS / OUTBOX_STUCK_RETENTION_DAYS | 30 / 30 | Processed and permanently stuck outbox retention. |
See Storage and Backups for backup requirements and restore planning.
Monitoring
| Variable | Default | Purpose |
|---|---|---|
METRICS_ENABLED | true | Registers Prometheus metrics endpoints. They return 404 without a valid token. |
METRICS_TOKEN | Unset | Bearer token, at least 16 characters, required for scraping. |
COMPOSE_PROFILES | Unset | Include monitoring to start Grafana, Prometheus, Loki, Alloy, and exporters; include admin for the management portal. |
COMPOSE_PROJECT_NAME | kanera | Compose identity used by Alloy to select this deployment's logs. Check docker compose ls on managed platforms. |
GRAFANA_ADMIN_PASSWORD | Unset | Required when the monitoring profile is enabled. |
MONITORING_BIND_IP | 127.0.0.1 | Host interface for Grafana and Prometheus. Use a private VPN address, never 0.0.0.0 in production. |
GRAFANA_BIND_PORT / PROMETHEUS_BIND_PORT | 3000 / 9090 | Host ports for the two monitoring UIs. |
PROMETHEUS_RETENTION / PROMETHEUS_RETENTION_SIZE | 30d / 5GB | Time and disk ceilings for Prometheus data. |
GRAFANA_ROOT_URL | Unset | External Grafana URL when serving it through a proxy. |
POSTGRES_EXPORTER_DSN | Bundled database | Override for an external PostgreSQL server. |
REALTIME_EMIT_METRICS_ENABLED | false | Enable sampled realtime payload metrics. |
REALTIME_EMIT_METRICS_SAMPLE_RATE / REALTIME_EMIT_METRICS_MIN_BYTES | 0.01 / 0 | Realtime metrics sampling and minimum payload size. |
Optional management portal
The management portal is separate from tenant settings and has its own authentication boundary. Enable it only when operators need cross-organisation administration or audited support sessions.
| Variable | Default | Purpose |
|---|---|---|
ADMIN_WEB_ORIGIN | http://localhost:4300 | Public origin and CORS boundary for the admin console. |
ADMIN_API_PORT | 3002 | Internal admin API listener port when it runs outside the bundled wiring. |
ADMIN_COOKIE_DOMAIN | Unset | Cookie-domain override when the admin hostname is outside COOKIE_DOMAIN. |
ADMIN_JWT_SECRET | Unset | Required for the admin API and must differ from JWT_SECRET. |
ADMIN_EMAIL / ADMIN_PASSWORD | Unset | Seed the first superadmin only while the admin-user table is empty. They do not reset an existing account. |
ADMIN_JWT_ACCESS_TTL / ADMIN_JWT_REFRESH_TTL_DAYS | 15m / 7 | Admin access and refresh lifetimes. |
ADMIN_LOGIN_RATE_LIMIT_MAX / ADMIN_LOGIN_RATE_LIMIT_WINDOW_MS | 5 / 300000 | Per-IP admin login throttle. |
ADMIN_TRUST_PROXY | false | Trust the admin reverse proxy for real client IPs. |
SUPPORT_SESSION_TTL_MINUTES | 60 | Short-lived support-session lifetime; maximum eight hours and no refresh token. |
Keep the management portal private, use a separate hostname and secret, and review its audit log. The full deployment wiring is documented in DEPLOY.md and DOKPLOY_DEPLOY.md in the Kanera repository.
Apply and verify changes
Most changes require recreating the affected services:
docker compose config --quiet
docker compose up -d --build
docker compose ps
Check the relevant service logs after changing authentication, delivery, storage, API, MCP, or admin settings. A syntactically valid .env does not prove that external credentials or URLs work.