Environment Variables¶
$DEVENV_STATE refers to <project root>/.devenv/state — this is where devenv stores runtime data.
All settings can be overridden in .env. Defaults (where appropriate) are provided in devenv.nix at low priority so .env values take precedence.
file: prefix: Any env var can be prefixed with file: to read the value from a file at runtime (e.g. KLANGK_JWT_SECRET=file:/run/secrets/jwt). The file contents are stripped of leading/trailing whitespace. This works with secret management tools like agenix/sops that write decrypted secrets to files. If the file cannot be read, an error is logged and the value is treated as unset.
cmd: prefix: Any env var can be prefixed with cmd: to resolve the value by running a shell command at runtime and using its stdout (e.g. KLANGK_JWT_SECRET=cmd:aws secretsmanager get-secret-value --secret-id klangk/jwt | jq -r .SecretString). The stdout is stripped of leading/trailing whitespace. This lets values be fetched from external sources (vault CLIs, cloud secret managers, decryption tools) without materializing them to disk or a plain env var. The command runs via the shell (so pipes work) with a short timeout; only values an operator explicitly prefixes with cmd: are ever executed. If the command fails (non-zero exit, timeout, or execution error), an error is logged and the value is treated as unset.
Note: the prefixes are applied only where the backend reads a var through
resolve_env_value/resolve_file_value(the vast majority ofKLANGK_*vars). A couple of values are consumed out-of-band bynginx.shvia bash expansion (KLANGK_LLM_BASE_URL,KLANGK_LLM_API_KEY) — these are run through theklangk-resolve-valueconsole script (see below) at nginx config generation time, so the prefixes work there too. A small number of vars are read raw by design (e.g.KLANGK_TRUSTED_PROXY_CIDRS, which is a public CIDR list, not a secret).
klangk-resolve-value console script: a CLI twin of resolve_file_value (registered as klangk-resolve-value = "klangk_backend._resolve_value:main" in src/backend/pyproject.toml) that resolves a single prefixed value from its single argument and prints the result to stdout (file: → file contents; cmd: → command stdout; plain → verbatim; failure → empty + reason on stderr). It is the single source of truth consumed by nginx.sh; operators rarely need to call it directly, but it is available on the PATH wherever the backend is installed:
klangk-resolve-value 'file:/run/secrets/jwt'
klangk-resolve-value 'cmd:aws secretsmanager ... | jq -r .SecretString'
klangk-resolve-value 'plain-value' # -> plain-value (verbatim)
| Variable | Default | Description |
|---|---|---|
KLANGK_NGINX_PORT |
8995 |
Primary access point — nginx reverse proxy port (UI, API, WebSocket, hosted apps) |
KLANGK_PORT |
8997 |
Backend (FastAPI/uvicorn) port — proxied through nginx, not accessed directly |
KLANGK_DATA_DIR |
$DEVENV_STATE/klangk/data |
Database, workspaces, Pi sessions |
KLANGK_CUSTOMIZE_DIR |
~/.klangk/custom |
Root directory for deployment customization files. Subsystems look for well-known subdirectories (certs/, branding/, email-templates/) here when their per-feature env var is unset. Missing subdirectories are silently skipped. One mount point replaces three. See Customizing a Deployment. |
KLANGK_PLUGINS_DIR |
$DEVENV_STATE/klangk/plugins |
Fetched plugins (outside repo for execIfModified) |
KLANGK_HOST_IMAGE |
klangk-host |
Docker image name for run-host-container |
KLANGK_VARIANT |
Identifies a custom build (a variant) of klangk, independent of the upstream klangk version. Baked into version.json as variant by scripts/generate-version.sh at build time; surfaced at GET /api/v1/version and the debug pane's Variant row. Empty/absent = stock klangk (no variant reported, output byte-identical to a non-customized build). See Customizing a Deployment. |
|
KLANGK_IMAGE_NAME |
klangk-workspace |
Podman image name for workspace containers |
KLANGK_IMAGE_PULL_POLICY |
never |
Podman --pull policy for workspace containers (never, missing, always, newer). Default never requires the image to exist locally; missing pulls from a registry if not found |
KLANGK_USERNS |
keep-id:uid=1000,gid=1000 |
Podman --userns mode for workspace containers. Maps the host user to uid/gid 1000 inside the container so bind-mounted files are owned correctly. |
KLANGK_PODMAN_BIN |
podman |
Path to the podman binary |
KLANGK_PORT_RANGE_START |
9000 |
First host port for workspace app allocations. Each workspace gets 5 ports starting from this value. See Hosted Apps. |
KLANGK_HOSTED_PORTS_PER_WORKSPACE |
5 |
Server-wide ceiling on hosted-app ports per workspace. Set to 0 to disable hosted-app serving entirely — no ports are allocated, no hosting env is injected into containers, and /hosted/... returns 404. Any other value clamps each workspace down to this cap. See Hosted Apps. |
KLANGK_ALLOWED_MOUNT_ROOTS |
Comma-separated list of allowed host path prefixes for bind mounts (e.g., /home,/data). If unset, all bind mount paths are allowed. Protected paths are always blocked. |
|
KLANGK_DNS_SERVERS |
Comma-separated DNS server IPs for containers (e.g., 100.100.100.100,8.8.8.8 for Tailscale MagicDNS). If unset, containers use podman's default DNS. |
|
KLANGK_HOSTING_HOSTNAME |
(auto-derived) | URL authority (host[:port]) for hosted-app and user-facing URLs — the port lives here, it is never synthesized from KLANGK_NGINX_PORT (that is internal container wiring, not the public port). Auto-derived from X-Forwarded-Host or the Host header (used verbatim, including their port); set explicitly in production. Behind a real reverse proxy this must carry the public host and port. |
KLANGK_HOSTING_PROTO |
(auto-derived) | Protocol for user-facing app URLs. Auto-derived from X-Forwarded-Proto or defaults to http. |
KLANGK_HOSTING_BASE_PATH |
(auto-derived) | Base path prefix for user-facing app URLs (e.g., /klangk). Auto-derived from X-Forwarded-Prefix. |
KLANGK_TRUSTED_PROXY_CIDRS |
127.0.0.1,::1 |
Comma-separated CIDRs/IPs whose connections are trusted to supply X-Forwarded-Host/Proto/Prefix headers. klangk's nginx proxies from loopback, so the default works out of the box; requests from peers outside this set have forwarded headers ignored (preventing host poisoning if the backend port is exposed). |
KLANGK_REJECT_PROXY_HEADERS |
Set to 1 to ignore X-Forwarded-Host/Proto/Prefix headers unconditionally (hard override; back-compat with the old opt-out). |
|
KLANGK_IDLE_TIMEOUT_SECONDS |
1800 |
Container idle timeout in seconds (check interval auto-computed as timeout/3, clamped 10-60s) |
KLANGK_LOGIN_LOCKOUT_WINDOW |
300 |
Time window in seconds for counting failed login attempts. |
KLANGK_LOGIN_LOCKOUT_FAILURES |
5 |
Number of failed login attempts before a lockout. Default 5 (enabled). Set to 0 to disable. |
KLANGK_LOGIN_LOCKOUT_DURATION |
900 |
Duration of lockout in seconds (only relevant when KLANGK_LOGIN_LOCKOUT_FAILURES > 0). |
KLANGK_LLM_API_KEY |
LLM provider API key | |
KLANGK_LLM_BASE_URL |
LLM API URL — must use IP or public FQDN, not bare hostnames (see Tailscale note) | |
KLANGK_LLM_MODEL |
LLM model name | |
KLANGK_AGENT_EMAIL |
clanker@example.com |
Email for the chat AI agent. Used for seeding — the agent user is created or updated in the DB on each startup. |
KLANGK_AGENT_HANDLE |
clanker |
@mention handle for the chat AI agent. Used for seeding — the agent user is created or updated in the DB on each startup. |
KLANGK_AGENT_DISABLED |
Set to 1/true/yes to disable the chat agent: the pi --mode rpc subprocess is not started, so the agent never comes online. Checked each time the subprocess would start. See Chat. |
|
KLANGK_JWT_SECRET |
JWT signing secret. A warning is logged at startup if unset or left as the insecure dev default. | |
KLANGK_PREVENT_INSECURE_JWT_SECRET |
Set to 1 to fail at startup if KLANGK_JWT_SECRET is unset or insecure. Recommended for production. |
|
KLANGK_ACCESS_TOKEN_HOURS |
24 |
Lifetime in hours for user session JWTs. Tokens are automatically refreshed before expiry. |
KLANGK_WORKSPACE_TOKEN_HOURS |
24 |
Lifetime in hours for per-workspace JWTs. Tokens are automatically renewed at 80% of their lifetime. |
KLANGK_DEFAULT_USER |
Auto-seeded admin email on startup | |
KLANGK_DEFAULT_PASSWORD |
Auto-seeded password on startup (omit to generate random; supports file:/cmd: prefix) |
|
KLANGK_MIN_PASSWORD_LENGTH |
8 |
Minimum password length |
KLANGK_FILE_UPLOAD_SIZE_MAX |
524288000 |
Maximum upload size in bytes for file uploads and workspace imports (default 500 MB) |
KLANGK_WS_MSG_SIZE_MAX |
16777216 |
Maximum WebSocket message size in bytes (default 16 MB). Increase if syncing very large files via rsync over WebSocket. |
KLANGK_CORS_ORIGINS |
Comma-separated list of allowed CORS origins (e.g., https://klangk.example.com). If unset, derived from KLANGK_HOSTING_HOSTNAME/KLANGK_HOSTING_PROTO, or defaults to http://localhost:{KLANGK_NGINX_PORT}. Set to * to allow all origins (not recommended for public deployments). |
|
KLANGK_TRUST_OUTER_PROXY |
Set to 1 (or true/yes) to pass through X-Forwarded-* headers from a trusted outer reverse proxy in front of klangk's nginx. By default klangk's nginx overwrites these with authoritative values ($http_host, $scheme) to prevent host poisoning. Only enable if the outer proxy itself overwrites, not passes through, these headers. |
|
KLANGK_GITHUB_OAUTH_CLIENT_ID |
GitHub OAuth App client ID for device-flow authentication. Injected into workspace containers by the git-credential plugin. When set, git push to github.com triggers the OAuth device flow automatically. No client secret needed. See GitHub Authentication. |
|
KLANGK_DISABLE_REGISTRATION |
Set to any non-empty value to block new user signups and hide the registration link in the UI | |
KLANGK_DISABLE_INVITES |
Set to 1 to disable the invitation system |
|
KLANGK_INVITE_EXPIRE_HOURS |
72 |
How long invitation links remain valid (hours) |
KLANGK_ALLOW_AUTOSTART |
Set to 1/true/yes to allow workspaces to be configured for auto-start on server boot. Default: disabled — the auto-start option is hidden in the UI, CLI, and API. See Auto-start. |
|
KLANGK_ALLOW_SUDO |
Set to 1/true/yes to grant passwordless sudo inside workspace containers. Default: disabled — an explicit deny rule (!ALL) is written so sudo is blocked even if a password is set on the klangk user. Configured at container start via podman exec --user root. |
|
KLANGK_DISABLE_TMUX |
Set to 1/true/yes to drop new terminals straight into a plain login shell instead of tmux. Only affects the default per-user terminal; shared/joined terminals always use tmux. |
|
KLANGK_OIDC_CONFIG |
Path to OIDC provider config file (YAML or JSON). Enables OIDC authentication when set. See OIDC Configuration. | |
KLANGK_OIDC_LOGIN_HOOK |
File path to a Python login hook script (e.g., /etc/klangk/login_hook.py). Optionally append :func_name (defaults to on_login). Called on each OIDC login to enforce custom policies. See OIDC Configuration. |
|
KLANGK_AUTH_MODES |
both (if OIDC configured) |
Auth modes: password, oidc, or both. Defaults to password when no OIDC config. |
KLANGK_SMTP_HOST |
SMTP server hostname (if set, uses SMTP; otherwise uses sendmail) | |
KLANGK_SMTP_PORT |
587 |
SMTP server port |
KLANGK_SMTP_USER |
SMTP auth username | |
KLANGK_SMTP_PASSWORD |
SMTP auth password | |
KLANGK_SMTP_FROM |
Email sender address (falls back to SMTP_USER, then noreply@localhost) | |
KLANGK_SMTP_USE_TLS |
true |
Use STARTTLS for SMTP |
KLANGK_SENDMAIL_PATH |
sendmail |
Path to sendmail binary (used when KLANGK_SMTP_HOST is not set) |
KLANGK_SSL_CERT_DIR |
Deprecated — use <KLANGK_CUSTOMIZE_DIR>/certs/ instead. Directory of .pem/.crt CA certificate files to trust at runtime. When unset, falls back to <KLANGK_CUSTOMIZE_DIR>/certs/. See Custom CA Certificates. |
|
KLANGK_EMAIL_TEMPLATES_DIR |
Deprecated — use <KLANGK_CUSTOMIZE_DIR>/email-templates/ instead. Directory of Jinja2 email template overrides. When unset, falls back to <KLANGK_CUSTOMIZE_DIR>/email-templates/. See Email Templating. |
|
KLANGK_PRODUCT_NAME |
Klangk |
White-label product name shown in the browser tab title, app-bar logo, and outgoing emails. Supports file:/cmd: prefix. See Customizing a Deployment. |
KLANGK_LOGIN_BANNER_TITLE |
Title shown on the consent banner page (e.g., company name). If empty, no title is displayed. | |
KLANGK_LOGIN_BANNER |
Consent banner text shown before login. Blocks all access until accepted. Supports file:/cmd: prefix. If empty, no banner is shown. |
|
KLANGK_TERMS_URL |
Terms of Service link rendered on the login/registration screens and in email footers. Plain value (no file:/cmd: resolution — it is public, shown pre-auth). Empty hides it. See #1177. |
|
KLANGK_PRIVACY_URL |
Privacy Policy link. Same semantics as KLANGK_TERMS_URL (plain value, hidden when empty). |
|
KLANGK_AUP_URL |
Acceptable Use Policy link. Same semantics as KLANGK_TERMS_URL. |
|
KLANGK_SUPPORT_URL |
Support/help link shown as a help icon in the app bar (authenticated) and on the auth screens. Plain value; hidden when empty and no KLANGK_SUPPORT_EMAIL is set. |
|
KLANGK_SUPPORT_EMAIL |
Support email address; shown as a mailto: when KLANGK_SUPPORT_URL is unset. Plain value; hidden when empty. |
|
KLANGK_TERMINAL_BANNER |
Text displayed in every terminal session on shell init. Supports file:/cmd: prefix. If empty (default), no banner is shown. |
|
KLANGKC_DEBUG_SSH_AGENT |
Set to any non-empty value to enable verbose debug logging for SSH agent forwarding on both the backend and CLI side. Useful for diagnosing relay issues. | |
KLANGK_WS_DEBUG |
Set to any non-empty value to enable verbose WebSocket message logging on the backend. Useful for diagnosing connection issues. | |
LOGFIRE_TOKEN |
Pydantic Logfire write token (opt-in) | |
LOGFIRE_BASE_URL |
https://logfire-api.pydantic.dev |
Logfire API base URL (for self-hosted instances) |
LOGFIRE_ENVIRONMENT |
Logfire environment tag (e.g., production, staging) — filters traces in the dashboard. |