DocsReference
Environment reference
From docs/ENV.md in the repository.
On this page
- How settings reach the containers
- Generated secrets
- Address, TLS and proxy
- Secrets and keys
- Database
- Email (installation default)
- Backups
- Attachments
- Rate limits
- Sign-in
- Cloud: deployment mode, signup and organization lifecycle
- Notifications and webhooks
- End-of-life data (endoflife.date)
- Preview features (off by default)
- The assistant (enterprise, off by default)
- Processes, logs and images
- The MCP server
- Development and tests only
Every environment variable Softwarden reads, its default, what it does, which service reads it and whether it is a secret. apps/api/tests/test_env_reference.py checks that this file and the code agree: a variable the code reads but this file doesn’t list fails the build, and so does a variable listed here that nothing reads any more.
How settings reach the containers
- No
.envis needed. On first start the one-shotinitservice generates the secrets (table below) into thesoftwarden-secretsvolume. A value set in.envor the shell wins over a generated one. .envsits next tocompose.yaml. Compose reads it for the${...}values incompose.yaml, and passes every line of it intoinit,appandworker(env_file). So any variable below marked app or worker can go in.env, even ifcompose.yamldoesn’t name it. Runchmod 600 .envonce it holds a password.- The shell environment reaches the containers only through
compose.yaml’s${...}values. A variablecompose.yamldoesn’t name must go in.env(or acompose.override.yamlenvironment:entry). - After changing
.env, rundocker compose up -d. Compose recreates the containers whose settings changed. - Defaults. Two defaults are given where they differ: the one on the Docker stack (set by
compose.yamlor theDockerfile), and the one outside Docker (settings.py, forrunserverand the tests).
Read by names the service: app (web and API, gunicorn), worker (schedules and jobs), init (generates secrets), proxy (Caddy), mcp (the MCP server, --profile mcp), or compose (used only by compose.yaml itself).
Generated secrets
File in softwarden-secrets |
Overridden by | Purpose |
|---|---|---|
django_secret_key |
SOFTWARDEN_SECRET_KEY |
Django’s signing key (sessions, CSRF). |
postgres_password |
POSTGRES_PASSWORD |
The database password. PostgreSQL reads it once, when the database is created. |
credential_keys |
SOFTWARDEN_CREDENTIAL_KEYS, SOFTWARDEN_CREDENTIAL_KEYS_FILE |
The Fernet key that encrypts secrets typed into Softwarden. |
backup_key |
SOFTWARDEN_BACKUP_KEY_FILE |
The key the nightly backups are encrypted with. |
ops_token |
SOFTWARDEN_OPS_TOKEN |
The bearer token for /healthz/ops, the operator health view. Read it with docker compose exec app cat /data/secrets/ops_token. |
bootstrap_admin prints the credential key once. The backup key is copied off the host as the install guide says. Neither is in a database backup, and docker compose down -v deletes both.
Address, TLS and proxy
| Variable | Default | Purpose | Read by | Secret |
|---|---|---|---|---|
PUBLIC_HOSTNAME |
localhost |
The DNS name people open. Caddy serves it, and the app’s allowed host, CSRF origin and base URL follow it unless set themselves. | compose, proxy, mcp | No |
HTTP_PORT |
80 |
Host port for plain HTTP, which only redirects to HTTPS. | compose | No |
HTTPS_PORT |
443 |
Host port for HTTPS. If you change it, set SOFTWARDEN_BASE_URL=https://host:port too. |
compose, proxy | No |
CADDY_TLS |
internal |
The argument of Caddy’s tls directive. internal uses Caddy’s own local CA, so browsers warn. /certs/cert.pem /certs/key.pem uses your certificate files (TLS_CERT_DIR). An email address, such as you@example.com, gets a Let’s Encrypt certificate for PUBLIC_HOSTNAME; ports 80 and 443 must then be reachable from the internet. The Let’s Encrypt form hasn’t been tried on a VM yet. |
proxy | No |
TLS_CERT_DIR |
./certs |
Host folder mounted at /certs in the proxy, for CADDY_TLS. |
compose | No |
SOFTWARDEN_ALLOWED_HOSTS |
Docker: ${PUBLIC_HOSTNAME},127.0.0.1. Outside: localhost,127.0.0.1 |
Host names the app answers, comma-separated. | app, worker | No |
SOFTWARDEN_CSRF_TRUSTED_ORIGINS |
Docker: https://${PUBLIC_HOSTNAME}. Outside: http://localhost:5173,http://127.0.0.1:5173 |
Origins allowed to send signed-in writes. Must match the browser’s address exactly. | app | No |
SOFTWARDEN_BASE_URL |
Docker: https://${PUBLIC_HOSTNAME}. Outside: http://localhost:8080 |
The address people browse to. Email links, the SSO redirect, SAML and SCIM addresses start with it. | app, worker | No |
SOFTWARDEN_SECURE_COOKIES |
Docker: 1. Outside: 0 |
Marks the session and CSRF cookies Secure (HTTPS only). | app | No |
SOFTWARDEN_HSTS_SECONDS |
0 (off). Helm: 31536000 while the ingress has TLS |
The app sends Strict-Transport-Security: max-age=<this> on HTTPS requests. Leave it unset on Compose: Caddy sends HSTS for a real host name and never for localhost. |
app | No |
SOFTWARDEN_TRUSTED_PROXIES |
Docker: proxy. Outside: empty (trust none) |
Host names, addresses or CIDR ranges allowed to set X-Forwarded-For, X-Forwarded-Proto and X-Request-ID. |
app | No |
SOFTWARDEN_MAX_REQUEST_MB |
Docker: 26. Outside: SOFTWARDEN_ATTACHMENT_MAX_MB + 1, at least 6 |
The largest request body read at all, in MiB. Caddy enforces the same limit. Keep it at least the attachment limit + 1. | app, proxy | No |
SOFTWARDEN_ALLOW_PRIVATE_SERVICE_HOSTS |
1; 0 in cloud mode |
Whether an organization’s own SMTP relay or Halo URL may be on a private network. Off for a hosted, multi-organization install (cloud mode’s default; doctor warns if it is on there). |
app, worker | No |
Secrets and keys
| Variable | Default | Purpose | Read by | Secret |
|---|---|---|---|---|
SOFTWARDEN_SECRET_KEY |
the generated django_secret_key |
Django’s secret key. The app refuses to start without one when debug is off. | init, app, worker | Yes |
SOFTWARDEN_CREDENTIAL_KEYS |
the generated credential_keys |
Fernet keys, comma-separated and newest first. The first encrypts; every key decrypts. | init, app, worker | Yes |
SOFTWARDEN_CREDENTIAL_KEYS_FILE |
empty | Path inside the containers to a file of keys, one per line. It wins over the variable above. Mount the file into app, worker and init yourself. |
init, app, worker | No (a path; the file is) |
SOFTWARDEN_SECRETS_KEY |
empty | The pre-Phase-1 key. It still decrypts what it encrypted and never encrypts anything new. | app, worker | Yes |
SOFTWARDEN_SECRETS_DIR |
/data/secrets |
Where the generated secret files are. | init, app, worker | No |
SOFTWARDEN_DB_SECRETS_DIR |
unset (/data/db-secrets in compose) |
The database container’s own secrets volume: init copies postgres_password there and nothing else, so the db container never mounts the other secrets. Unset, no copy is made. |
init | No |
SOFTWARDEN_PGDATA |
/pgdata |
The database volume, mounted read-only into init, so it can tell a new database from an existing one. |
init | No |
SOFTWARDEN_OPS_TOKEN |
the generated ops_token, else empty |
Bearer token for /healthz/ops. On a single-organization installation an Admin session also opens it. |
init, app, worker | Yes |
BOOTSTRAP_ADMIN_PASSWORD |
empty | The first Admin’s password for bootstrap_admin --create --no-input (unattended setup). In cloud mode, the platform staff account’s. |
app (command) | Yes |
BOOTSTRAP_ADMIN_EMAIL |
empty | With bootstrap_admin --create --no-input: the first account’s email when --email isn’t given (for a Helm Job or other unattended run). --email wins. |
app (command) | No |
BOOTSTRAP_ADMIN_NAME |
empty | As above, the display name when --name isn’t given. --name wins. |
app (command) | No |
SOFTWARDEN_BOOTSTRAP_TOKEN_FILE |
Docker: /data/bootstrap/setup-token. Outside: apps/api/.bootstrap/setup-token |
The private file holding the one-time first-run setup token. | app | No (a path) |
Database
| Variable | Default | Purpose | Read by | Secret |
|---|---|---|---|---|
POSTGRES_PASSWORD |
the generated postgres_password. Outside: softwarden-dev |
The database password. init copies it into the secrets volume while no database exists yet. Once a database exists, a different value stops start-up. |
init, app, worker | Yes |
POSTGRES_HOST |
Docker: db. Outside: 127.0.0.1 |
The database host. | app, worker | No |
POSTGRES_PORT |
Docker: 5432 inside the stack. Outside: 5434 |
Outside Docker, the port the API connects to. In compose.dev.yaml it sets the loopback host port the db service is published on (default 5434); compose.yaml doesn’t publish the database. The app inside the stack always uses 5432. |
app, worker, compose | No |
POSTGRES_DB |
softwarden |
The database name. The bundled db service always creates softwarden, so change it only for an external PostgreSQL. |
app, worker | No |
POSTGRES_USER |
softwarden |
The database role, as for POSTGRES_DB. |
app, worker | No |
SOFTWARDEN_DB_CONN_MAX_AGE |
0; 60 in cloud mode |
Seconds a connection is kept for reuse. 0 opens a new one per request. A kept connection is checked before reuse. Cloud mode keeps them because a managed server is across a network and every new connection costs a TLS handshake. | app, worker | No |
POSTGRES_SSLMODE |
empty (libpq’s PGSSLMODE, else prefer) |
libpq’s sslmode for the app’s connection: disable, allow, prefer, require, verify-ca or verify-full. Use require or stronger for a managed server; cloud mode’s doctor fails anything weaker. The worker’s pg_dump and pg_restore get the same value. |
app, worker | No |
POSTGRES_SSLROOTCERT |
empty | Path inside the containers to the server’s CA bundle, for verify-ca and verify-full. Mount the file yourself (Helm: extraVolumes). |
app, worker | No (a path) |
SOFTWARDEN_DB_JIT |
off |
off sends -c jit=off for the app’s sessions. Set on behind a pooler that refuses the options parameter (PgBouncer, Azure’s built-in pooler); then run ALTER ROLE ... SET jit = off on the server instead. |
app, worker | No |
libpq’s own variables also apply, because the app (psycopg) and the worker’s pg_dump and pg_restore all use libpq:
PGSSLMODE,PGSSLROOTCERT,PGSSLCERTandPGSSLKEYfor a server that requires TLS. The backup passes exactly these four to its tools, and no other inherited variable.POSTGRES_SSLMODEandPOSTGRES_SSLROOTCERTwin over the first two, for the app and the tools alike.PGCONNECT_TIMEOUT.manage.py doctorsets it to 5 seconds for its own run, unless it is already set.
Mount any certificate files into app and worker yourself. The Helm chart does this through extraVolumes and extraVolumeMounts.
A connection pooler (PgBouncer, a managed server’s built-in pooler) must run in session mode for the app and the worker: both take session advisory locks (the backup lock, the Graph mail slots), which transaction pooling would leak (the cloud guide).
Server settings on a managed PostgreSQL: never set idle_in_transaction_session_timeout below 20 seconds for the app’s
role. Public signup sends its confirmation email inside its transaction (so a refused message rolls the new link back),
and holds it for up to 20 seconds against a slow SMTP server (notify/transactional.DEADLINE).
Email (installation default)
An organization can send through its own Microsoft Graph or SMTP settings instead (Administration › Email settings). These variables apply to every organization that uses the installation default.
| Variable | Default | Purpose | Read by | Secret |
|---|---|---|---|---|
SOFTWARDEN_SMTP_HOST |
empty (email off) | The SMTP server. While it is empty, digests and reminders are recorded as not sent, and every action stays in Needs attention. | app, worker | No |
SOFTWARDEN_SMTP_PORT |
587 |
SMTP port. | app, worker | No |
SOFTWARDEN_SMTP_USER |
empty | SMTP user name. | app, worker | No |
SOFTWARDEN_SMTP_PASSWORD |
empty | SMTP password. It is never logged or returned. | app, worker | Yes |
SOFTWARDEN_SMTP_TLS |
1 |
Use STARTTLS. | app, worker | No |
SOFTWARDEN_EMAIL_FROM |
Softwarden <softwarden@localhost> |
The From address. | app, worker | No |
SOFTWARDEN_EMAIL_BACKEND |
the SMTP backend while SOFTWARDEN_SMTP_HOST is set, else none |
A Django email backend path. For development and tests only. | app, worker | No |
Backups
Read by the worker’s nightly backup. The app reads them too, to show backup status on the System page. Details are in the backup runbook.
| Variable | Default | Purpose | Read by | Secret |
|---|---|---|---|---|
SOFTWARDEN_BACKUP_DIR |
Docker: /data/backups (the softwarden-backups volume). Outside: empty (not configured) |
Where backup sets are written. | worker, app | No |
SOFTWARDEN_BACKUP_KEY_FILE |
the generated backup_key |
Path inside the containers to your own key file. Mount it into worker and app, mode 600, owned by uid 10001. |
init, app, worker | No (a path; the file is) |
SOFTWARDEN_BACKUP_CRON |
30 2 * * * |
When the nightly backup runs, as five-field cron in UTC. | worker, app | No |
SOFTWARDEN_BACKUP_KEEP_DAILY |
14 |
Daily sets kept. | worker | No |
SOFTWARDEN_BACKUP_KEEP_WEEKLY |
8 |
Weekly (Sunday) sets kept. | worker | No |
SOFTWARDEN_BACKUP_STALE_HOURS |
26 |
After this many hours with no good backup, the System page warns. | app, worker | No |
SOFTWARDEN_BACKUP_MIN_FREE_MB |
unset: the larger of 2 GB and 10 % of the disk | Free space that must remain on the backup disk after a set. | worker | No |
SOFTWARDEN_BACKUP_REQUIRE_MOUNT |
1 |
Refuse a destination that isn’t a mounted volume. Set 0 for development only. | worker | No |
SOFTWARDEN_BACKUP_PG_DUMP |
pg_dump |
The pg_dump command. |
worker | No |
SOFTWARDEN_BACKUP_PG_RESTORE |
pg_restore |
The pg_restore command, used to verify each dump. |
worker | No |
SOFTWARDEN_BACKUP_OPENSSL |
openssl |
The openssl command. |
worker | No |
The host scripts in ops/ (ops/backup.sh, ops/restore.sh) read their own variables (BACKUP_DEST, BACKUP_KEY_FILE, SOFTWARDEN_DIR, BACKUP_KEEP_DAILY, BACKUP_KEEP_WEEKLY) from ops/backup.env. They are listed in the backup runbook.
Attachments
| Variable | Default | Purpose | Read by | Secret |
|---|---|---|---|---|
SOFTWARDEN_MEDIA_ROOT |
Docker: /data/media (the softwarden-media volume). Outside: apps/api/media |
Where attachment files are stored. | app, worker | No |
SOFTWARDEN_ATTACHMENT_MAX_MB |
25 |
The largest attachment. Above 25, raise SOFTWARDEN_MAX_REQUEST_MB too. |
app | No |
SOFTWARDEN_ATTACHMENT_EXTENSIONS |
pdf,docx,xlsx,pptx,vsdx,doc,xls,png,jpg,jpeg,gif,txt,csv,md,json,zip |
Accepted types. Only types with a content check are allowed, so never html or svg. | app | No |
SOFTWARDEN_ATTACHMENT_QUOTA_MB |
10240 |
Storage per organization. 0 means no quota. | app | No |
SOFTWARDEN_ATTACHMENT_RETENTION_DAYS |
7 |
Days a deleted attachment’s file is kept before the nightly sweep removes it. | worker | No |
SOFTWARDEN_ATTACHMENT_UPLOAD_RATE |
60/hour |
Uploads per user. | app | No |
Rate limits
DRF rate syntax: n/sec, n/min, n/hour or n/day. Limits are counted per server process. An empty value means no limit, except for the sign-in limit.
| Variable | Default | Purpose | Read by | Secret |
|---|---|---|---|---|
LOGIN_THROTTLE_RATE |
10/min |
Sign-in attempts per client address. Also confirming a signup link, accepting an invitation, and asking to delete an organization (per account). | app | No |
SOFTWARDEN_SIGNUP_THROTTLE_RATE |
10/hour |
Public signups and confirmation resends per client address (cloud). | app | No |
SOFTWARDEN_API_READ_RATE |
1200/min |
API reads per token or session user. | app | No |
SOFTWARDEN_API_WRITE_RATE |
240/min |
API writes per token or session user. | app | No |
SOFTWARDEN_SEARCH_RATE |
600/min |
Global search requests per user. | app | No |
SOFTWARDEN_CALENDAR_FEED_RATE |
60/hour |
Requests per renewals calendar feed. | app | No |
SOFTWARDEN_CALENDAR_FEED_ADDRESS_RATE |
1200/hour |
Calendar feed requests per client address, across all feeds. | app | No |
SOFTWARDEN_SCIM_RATE |
600/min |
Requests per SCIM token. | app | No |
SOFTWARDEN_SCIM_MAX_BODY |
1048576 |
The largest SCIM request body, in bytes. | app | No |
Sign-in
Each organization sets up its own single sign-on in Administration › Sign-in. The variables below are installation-wide.
| Variable | Default | Purpose | Read by | Secret |
|---|---|---|---|---|
SOFTWARDEN_REQUIRE_IF_MATCH |
off | 1: a signed-in session’s PATCH or DELETE (and every action that takes If-Match) without If-Match is refused with 428 /problems/precondition-required, so an edit form that never read the record’s ETag can’t silently overwrite someone else’s change. API tokens (scripts, the MCP server) keep the unconditional write. Leave it off until every web app edit form sends If-Match (API revisions). |
app | No |
SOFTWARDEN_FORCE_LOCAL_LOGIN |
off | Break-glass. Hides and refuses every single sign-on route; password sign-in always works. See the SSO recovery runbook. | app | No |
SOFTWARDEN_SSO_DOMAIN_VERIFICATION |
off; on in cloud mode | A sign-in domain an organization claims stays pending (it finds no organization on the sign-in page and blocks nobody) until a DNS TXT record _softwarden-challenge.<domain> proves the organization controls it (POST sso/settings/domains/verify); unverified claims lapse after 14 days (removing a domain and adding it again makes a new claim, whose 14 days start again, so the lapse clears forgotten claims rather than capping how long one organization can keep one pending). Off: a claim applies at once and the first organization keeps the domain. |
app | No |
SOFTWARDEN_DNS_RESOLVER |
simulated with DEBUG on, else empty |
The resolver for those TXT lookups: host or host:port; empty uses the first nameserver in /etc/resolv.conf; simulated makes no network call (development and tests). Standard library UDP, then TCP for a truncated answer, 3 s per attempt; only an answer from that resolver’s address and port to this exact question counts. doctor warns when it is simulated with SOFTWARDEN_DEBUG off. |
app | No |
SOFTWARDEN_OIDC_TENANT |
empty | Deprecated. Legacy installation-wide Entra sign-in: the tenant GUID. Used only by a single-organization install that hasn’t activated its own settings. | app | No |
SOFTWARDEN_OIDC_CLIENT_ID |
empty | Deprecated. The legacy sign-in app’s client ID. | app | No |
SOFTWARDEN_OIDC_CLIENT_SECRET |
empty | Deprecated. The legacy sign-in app’s client secret. | app | Yes |
Cloud: deployment mode, signup and organization lifecycle
For the hosted service (Phase 10, the cloud guide). A self-hosted install leaves all of these at their defaults.
| Variable | Default | Purpose | Read by | Secret |
|---|---|---|---|---|
SOFTWARDEN_DEPLOYMENT |
selfhosted |
selfhosted or cloud (anything else stops start-up). cloud is the hosted, multi-organization service: public signup and kept database connections default on, private service hosts default off, first-run setup creates the operator’s platform staff account (no organization, no web setup), and doctor adds the hosted checks. It never sets an organization’s edition: signup creates cloud organizations whatever the mode. Helm: deployment. Compose passes it through from .env for evaluating cloud mode on one host. Cloud mode is not supported for production in 1.0 (docs/CLOUD.md). |
app, worker | No |
SOFTWARDEN_SIGNUP_ENABLED |
off; on in cloud mode | Public signup at /signup: anyone can create an account and a new organization (edition cloud) after confirming their email. Needs email (SOFTWARDEN_SMTP_HOST); without it signup refuses with a clear problem. |
app | No |
SOFTWARDEN_SIGNUP_DAILY_LIMIT |
200 |
While signup is on, at most this many organizations are created in 24 hours (counted from the database, so every pod agrees); past it signing up and confirming answer 503 /problems/signup-paused and pending signups keep their links. 0 turns the ceiling off. |
app | No |
SOFTWARDEN_ORG_DELETION_GRACE_DAYS |
30 (at least 1) |
Days between an Admin asking to delete their organization (capability org.self_delete) and the worker removing it. An Admin can cancel until then. |
app, worker | No |
SOFTWARDEN_IMPERSONATION_MINUTES |
30 (1 to 240) |
How long one impersonation by platform staff lasts (Phase 10 platform admin console); it ends on the first request after this. | app | No |
SOFTWARDEN_TENANT_BACKUP_RETENTION |
7 (1 to 90) |
How many nightly backups of each organization are kept (Phase 10 tenant backups, capability org.tenant_backups, cloud and enterprise); older backup rows and files are removed. Each backup holds the organization’s attachments, so the media volume needs about this many times each such organization’s data plus attachments (docs/04-development/platform-admin-runbook.md, Tenant backups). |
app, worker | No |
Notifications and webhooks
| Variable | Default | Purpose | Read by | Secret |
|---|---|---|---|---|
SOFTWARDEN_WEBHOOK_MAX_ATTEMPTS |
6 |
Attempts per webhook event delivery, from 1 to 20. | worker | No |
SOFTWARDEN_WEBHOOK_DISABLE_AFTER |
5 |
An endpoint turns off after this many failed deliveries in a row. 0 means never. | worker | No |
SOFTWARDEN_DELIVERY_RETENTION_SENT_DAYS |
30 |
Days sent notification deliveries are kept. | worker | No |
SOFTWARDEN_DELIVERY_RETENTION_FAILED_DAYS |
90 |
Days failed or skipped deliveries are kept. | worker | No |
End-of-life data (endoflife.date)
| Variable | Default | Purpose | Read by | Secret |
|---|---|---|---|---|
SOFTWARDEN_EOL_ENABLED |
1 |
Whether the worker’s daily eol.refresh job reads https://endoflife.date/api/v1 (the product list, then the release cycles of the products some organization tracks) and whether a new mapping queues its product. Set 0 on an air-gapped install and load a saved snapshot with manage.py eol_import instead (operations). The screens always read the cached data, never the network. |
app, worker | No |
Preview features (off by default)
| Variable | Default | Purpose | Read by | Secret |
|---|---|---|---|---|
SOFTWARDEN_PREVIEW_FEATURES |
off | Turns on the preview features for every organization: Okta and Google Workspace discovery (still enterprise capabilities discovery.okta and discovery.google) and ServiceNow and Halo ticket connections (capability preview). Off, they are hidden whatever an edition or override says: their routes answer 403, the schedules skip them, and stored connectors, connections and links are kept for when it is turned on (capabilities). |
app, worker | No |
The assistant (enterprise, off by default)
| Variable | Default | Purpose | Read by | Secret |
|---|---|---|---|---|
ANTHROPIC_API_KEY |
empty | The installation’s Anthropic key, for organizations that enter none of their own. It is used only while SOFTWARDEN_ASSISTANT_INSTALLATION_CAP_USD is set. |
app, worker | Yes |
SOFTWARDEN_ASSISTANT_MODEL |
claude-sonnet-5-5 |
The Claude model used for suggestions. | app, worker | No |
SOFTWARDEN_ASSISTANT_PRICES |
empty | Price overrides as JSON, in USD per million tokens, for a model that isn’t built in. | app, worker | No |
SOFTWARDEN_ASSISTANT_INSTALLATION_CAP_USD |
empty | Monthly ceiling per organization on the installation’s key. While it is unset, the key is never used. | app, worker | No |
Processes, logs and images
| Variable | Default | Purpose | Read by | Secret |
|---|---|---|---|---|
SOFTWARDEN_DEBUG |
0 (the image sets 0 too) |
Development mode: 1 turns it on (the development secret key and credential key, the sign-in simulators, and the simulated discovery and ticket connections). Set it for local development only (README, Run it locally); never on a server. Off, the API refuses to start without SOFTWARDEN_SECRET_KEY or the generated key. |
app, worker | No |
SOFTWARDEN_WORKERS |
3 |
gunicorn worker processes. | app | No |
SOFTWARDEN_THREADS |
8 |
Threads per gunicorn worker process (gthread). Each thread may keep a database connection, so the app can hold SOFTWARDEN_WORKERS × this many (24 by default; plan max_connections for it, docs/CLOUD.md “Database connections”). The proxy in front must buffer request bodies (Caddy’s request_buffers, an ingress’s request buffering). Helm: app.gunicornThreads. |
app | No |
SOFTWARDEN_WORKER_LONG_JOBS |
2 |
Long jobs (discovery runs) the worker runs at once, 1 or more. | worker | No |
SOFTWARDEN_SPA_DIR |
Docker: /app/web. Outside: apps/web/dist |
The built single-page app. | app | No |
SOFTWARDEN_LOG_FORMAT |
json |
json lines, or text for local reading. |
app, worker | No |
SOFTWARDEN_LOG_LEVEL |
INFO |
The root log level. | app, worker | No |
OTEL_EXPORTER_OTLP_ENDPOINT |
empty (tracing off) | OpenTelemetry OTLP/HTTP endpoint. It needs the image built with WITH_OTEL=1. The SDK’s other standard OTEL_* variables apply as it documents them. |
app, worker | No |
OTEL_SERVICE_NAME |
softwarden-web or softwarden-worker |
The service name in traces. | app, worker | No |
SOFTWARDEN_IMAGE |
softwarden:local |
The image init, app and worker run. On a server, pin a release tag (see the upgrade runbook). |
compose | No |
SOFTWARDEN_MCP_IMAGE |
softwarden-mcp:local |
The MCP server’s image. | compose | No |
The MCP server
Applies only with docker compose --profile mcp up -d, or to the softwarden-mcp command on a desktop. See MCP server.
| Variable | Default | Purpose | Read by | Secret |
|---|---|---|---|---|
SOFTWARDEN_MCP_UPSTREAM |
empty (/mcp answers 404) |
Where Caddy sends /mcp. Set mcp:8001 with the profile. |
proxy | No |
SOFTWARDEN_URL |
Docker: http://app:8000. Desktop: required |
The Softwarden address the MCP server calls. | mcp | No |
SOFTWARDEN_TOKEN |
empty | Desktop (stdio) only: the person’s API token (sw_...). Over HTTP, each client sends its own. |
mcp | Yes |
SOFTWARDEN_VERIFY_TLS |
true |
true, false (development only) or the path of a CA bundle. |
mcp | No |
SOFTWARDEN_HOST_HEADER |
Docker: ${PUBLIC_HOSTNAME}. Desktop: none |
The Host header sent while calling app:8000 inside the stack. |
mcp | No |
MCP_HOST |
Docker: 0.0.0.0. Desktop: 127.0.0.1 |
HTTP bind address. | mcp | No |
MCP_PORT |
8001 |
HTTP port. | mcp | No |
MCP_ENABLE_WRITES |
0 |
1 registers the write tools. Each still needs a token that isn’t read-only, and If-Match. |
mcp | No |
MCP_ALLOWED_HOSTS |
Docker: ${PUBLIC_HOSTNAME} with any port. Desktop: loopback names |
Host headers accepted (the DNS rebinding guard). | mcp | No |
MCP_FORWARDED_ALLOW_IPS |
empty. Docker: *. Helm: trustedProxies |
Proxies whose X-Forwarded-For names the client (addresses, CIDRs or *), so refused tokens are limited per client address. Empty and not bound to loopback: no per-address limit. |
mcp | No |
MCP_ALLOWED_ORIGINS |
Docker: https://${PUBLIC_HOSTNAME} with any port. Desktop: loopback origins |
Browser origins accepted. | mcp | No |
Development and tests only
| Variable | Default | Purpose | Read by | Secret |
|---|---|---|---|---|
SOFTWARDEN_ALLOW_SIMULATED |
off (compose.dev.yaml: 1) |
Allows the simulated tenants and hosts outside development: the Microsoft tenant ID simulated (connections, discovery, Graph email) and halo., okta., jira. and servicenow.simulated.invalid and an administrator in google.simulated.invalid. They never leave the machine and fill the catalog with synthetic Contoso data, so a production install leaves this off. Off, and outside SOFTWARDEN_DEBUG=1 and the Playwright server, a simulated value is refused on save (a field error naming this variable), at the connection test (simulation_off), and when a discovery run, a ticket refresh or a Graph email starts (the run fails with the reason); connections saved while it was on are kept and never run, and doctor warns about their connectors. seed_demo (all but --accounts-only), seed_scale and simulate_entra_change refuse without it, before writing anything. Set it for the Docker demo (README, “With the demo data”). It never turns on the sign-in simulators (OpenID Connect and SAML), which stay DEBUG or Playwright only. |
app, worker | No |
SOFTWARDEN_E2E |
empty | 1 turns on the Playwright-only routes and simulators, and only against a database whose name ends in _e2e. |
app | No |
Not listed in the tables, because they aren’t installation settings:
- the live-test credentials (
SOFTWARDEN_LIVE_*, README “Live tests”); - the setup scripts’
SOFTWARDEN_TLS_CHECK_HOST; - the Playwright ports (
apps/web); - the variables the PostgreSQL image reads (
compose.yamlfixes them).