Self-Hosted Environment Reference
Start with the release's env.example, which you downloaded as .env in Getting Started. 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 Settings -> Organisation -> Security. 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 | true | 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 in the Kanera repository.
Apply and verify changes
Most changes require recreating the affected services:
docker compose config --quiet
docker compose up -d
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.