Runtime configuration
TOW uses two runtime configuration layers:
tow.yamlfor non-secret application runtime settings..envfor production secrets and Compose bootstrap values such as the exactTOW_VERSION; local development uses.env.devinstead.
The backend reads both. Environment variables can still override matching YAML fields, but use that as a deployment escape hatch. The normal operating model is: put server configuration in tow.yaml, and put secret material in .env or your platform secret manager.
For the AI queue, periodic operating runner, and background job behavior controlled by these settings, see Background AI system.
.env
The installer writes .env with every secret generated. To
assemble it by hand instead, start from the documented template in the deploy
kit:
cp .env.example .env
The .env file should contain secrets and the small set of deployment values
Compose needs before the app can read tow.yaml:
| Variable | Purpose |
|---|---|
TOW_VERSION | Exact immutable release tag used by all TOW production images. |
APP_SECRET | Session and OIDC state signing secret. Generate a deployment-specific random value, for example with openssl rand -base64 48. The default placeholder is rejected at startup. |
POSTGRES_PASSWORD | Docker Postgres password; the Compose stack derives the backend's database connection string from it. |
OPENAI_API_KEY | Enables OpenAI model and embedding calls. |
EXA_API_KEY | Enables optional web search tools. |
SMTP_USERNAME, SMTP_PASSWORD | Optional SMTP credentials. |
EMAIL_GOOGLE_OAUTH_CLIENT_SECRET | Optional Google mailbox OAuth application secret. |
EMAIL_MICROSOFT_OAUTH_CLIENT_SECRET | Optional Microsoft mailbox OAuth application secret. |
OIDC_CLIENT_SECRET | Required when auth.mode: oidc; the OIDC client secret from authentik or the chosen identity provider. |
PUBLIC_APP_URL | Optional authentik bootstrap input; app runtime should still set runtime.public_app_url in tow.yaml. |
AUTHENTIK_BOOTSTRAP_TOKEN | Optional authentik bootstrap API token used by the Compose one-shot setup job. |
AUTHENTIK_BOOTSTRAP_PASSWORD | Optional initial authentik admin password for the bundled authentik profile. |
AUTHENTIK_BOOTSTRAP_EMAIL | Optional initial authentik admin email for the bundled authentik profile. |
TLS_ADDITIONAL_CA_CERTIFICATES | Optional env override for security.additional_ca_certificates. Prefer the YAML key: the value is multi-line PEM. See Trusting an internal certificate authority. |
Bootstrap and deployment wiring such as published ports, docs URL, proxy target, and TOW_CONFIG_PATH belong in Docker Compose, your process manager, or your platform configuration. Authentik bootstrap values are present in .env.example so the optional Compose profile can be started consistently, but production operators should still treat token, password, and secret values as secrets.
Never commit production .env files. They contain secrets.
tow.yaml
The installer renders config/tow.yaml from the deploy
kit's documented template. To create it by hand (for example when running
outside Docker):
cp config/tow.example.yaml config/tow.yaml
In the production Compose stack, TOW_CONFIG_PATH is set to:
/app/config/tow.yaml
The example YAML includes:
runtime:
app_name: TOW
backend_cors_origins:
- http://localhost:3000
- http://localhost:8000
trusted_proxy_cidrs:
- 127.0.0.1/32
- ::1/128
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
- fc00::/7
ai:
openai_reasoning_model: gpt-5.2
openai_fast_model: gpt-5.2
openai_title_model:
openai_onboarding_model:
openai_embedding_model: text-embedding-3-small
max_tool_rounds: 4
embedding_dim: 1536
processes: {}
security:
session_cookie_name: tow_session
session_cookie_secure: false
session_max_age_seconds: 2592000
# PEM bundle trusted in addition to public CAs for all outbound TLS
additional_ca_certificates:
auth:
mode: builtin
oidc:
issuer:
client_id:
scopes:
- openid
- profile
- email
username_claim: preferred_username
email_claim: email
groups_claim: groups
require_email_verified: false
email:
transport: console
from_address: TOW <noreply@example.com>
reply_to:
inbound:
worker_batch_size: 10
worker_poll_seconds: 5
worker_lease_seconds: 120
connect_timeout_seconds: 15
messages_per_poll: 50
max_message_bytes: 52428800
allowed_hosts: []
hosted_addresses: []
oauth:
google:
client_id:
microsoft:
client_id:
tenant: organizations
smtp:
host:
port: 587
tls_mode: starttls
Server admins can edit YAML-backed values from Admin, Server Settings. Those edits write to the configured YAML file.
Email transport, SMTP host, SMTP port, sender address, upload limits, session policy, authentication mode, OIDC issuer and claim names, AI model names, AI tool round limits, CORS origins, background job cadence, and additional trusted CA certificates for outbound TLS all belong here.
Project email channels always use the global outbound transport and SMTP
credentials. IMAP credentials and native-provider OAuth tokens are stored
separately in the database, encrypted with ORG_AUTH_SECRET_KEY, and are never
returned by the API. Set native mailbox client IDs under email.oauth; keep
their client secrets in .env or a platform secret manager.
The inbound email worker rejects IMAP hosts that resolve to loopback, private,
link-local, multicast, reserved, or unspecified addresses. A self-hosted
mailserver on such a network must be added deliberately to
email.inbound.allowed_hosts. Use exact hostnames or IP addresses and keep the
list as narrow as possible. Add addresses and domains operated by TOW to
email.inbound.hosted_addresses so messages from them cannot create requests
or become request participants.
Browser Web Push
Browser push is optional. It becomes available when a stable VAPID key pair and a separate subscription-encryption secret are configured. Generate them once from the backend image:
docker compose run --rm backend python -m app.scripts.generate_web_push_keys
Copy the three printed values into .env, then add a contact subject:
WEB_PUSH_VAPID_PUBLIC_KEY=...
WEB_PUSH_VAPID_PRIVATE_KEY=...
WEB_PUSH_SUBSCRIPTION_SECRET=...
WEB_PUSH_VAPID_SUBJECT=mailto:admin@example.com
Keep the private key and encryption secret stable and backed up. The private key authorizes the deployment to send, while the encryption secret protects stored browser endpoints and key material. The public key is intentionally returned to signed-in clients when push is available.
notifications.web_push in tow.yaml controls worker batch, poll, stale lease
timing, the consecutive-failure cutoff, and terminal-outbox retention. Permanent
provider failures and unreadable encrypted subscriptions disable that browser
until it registers again. Push registrations are bound to the login session and
stop delivering when that session expires or is revoked. Run one or more
web-push-worker processes. Browsers require a secure context (HTTPS, or
localhost for local development), and users must grant permission from the
Notifications settings page.
Authentication mode
TOW supports two authentication modes:
| Mode | Behavior |
|---|---|
builtin | Local email/password login and invite-based registration. This is the default for simple installs. |
oidc | Built-in login and registration are disabled. Users authenticate through authentik or another OIDC provider, then TOW creates its normal signed session cookie. |
Choose the mode before first boot. In builtin mode, the first registered user becomes the server admin and organisation owner. In oidc mode, the first successful OIDC login becomes the server admin and organisation owner. Later OIDC users must either have an existing linked identity or use an invite whose email matches the provider email claim.
Public self-service signup is opt-in. When auth.signup.enabled is true, users can create accounts from /login without an invite. TOW sends its own email verification link before creating a browser session. After verification, TOW first applies a valid invite, then joins exactly one organisation with a verified matching email domain, and otherwise creates a new workspace for the user.
Public signup requires a working runtime.public_app_url and email delivery. For OIDC/authentik mode, TOW still sends its own verification email even when the identity provider includes an email_verified claim. If new users do not already have identity-provider accounts, configure auth.signup.oidc_start_url to an authentik enrollment flow, for example https://auth.example.com/if/flow/tow-public-enrollment/. The bundled authentik bootstrap creates that flow when SIGNUP_ENABLED=true is present in the bootstrap job environment. auth.signup.enabled: true in tow.yaml enables TOW at runtime, but the one-off authentik-bootstrap container does not read tow.yaml; put SIGNUP_ENABLED=true in .env or pass it to the bootstrap command when creating/updating the Authentik flow. TOW will send unknown public-signup emails there first and pass authentik a next parameter that returns to the OIDC authorization flow.
The bundled tow-public-enrollment flow requires separate first and last name fields. After upgrading an existing authentik deployment, rerun authentik-bootstrap with SIGNUP_ENABLED=true so authentik receives the updated prompts and validation policy.
For OIDC mode, configure these YAML values:
runtime:
public_app_url: https://tow.example.com
auth:
mode: oidc
signup:
enabled: false
oidc_start_url: https://auth.example.com/if/flow/tow-public-enrollment/
verification_token_ttl_hours: 24
resend_cooldown_seconds: 60
oidc:
issuer: https://auth.example.com/application/o/tow/
client_id: tow
scopes:
- openid
- profile
- email
username_claim: preferred_username
email_claim: email
email_verified_claim: email_verified
groups_claim: groups
require_email_verified: false
provider_logout_enabled: false
Set OIDC_CLIENT_SECRET in .env or your secret manager. OIDC mode also requires a non-default APP_SECRET of at least 32 characters. runtime.public_app_url and the OIDC issuer must use HTTPS except for localhost development; the issuer must match the OIDC discovery document exactly. For authentik, the issuer is normally the provider URL under /application/o/<slug>/. auth.oidc.provider_logout_enabled is opt-in; when enabled and the provider advertises end_session_endpoint, logging out of TOW redirects through the provider and then back to /login?logged_out=1.
TOW stores authentik/OIDC claims on the linked identity record for audit and future mapping, but it does not map provider groups to roles automatically. TOW still owns organisations, memberships, roles, permissions, and application audit behavior.
Precedence
Configuration precedence is:
| Priority | Source |
|---|---|
| Highest | Explicit runtime values passed by code. |
| High | Environment variables. |
| Medium | .env values loaded by the backend process. |
| Low | tow.yaml. |
| Lowest | Built-in defaults. |
If a non-secret environment variable is set, the matching YAML value is ignored. This is the most common reason a Server Settings change appears to save but does not change runtime behavior after restart. Prefer removing the override and managing the value in YAML.
Restart requirements
Most runtime settings are read when the backend process starts. After changing YAML or environment values:
- Restart the backend.
- Confirm
/api/healthresponds. - Sign in and confirm expected behavior.
For a YAML-only change with an existing bind mount, restart the backend and its workers (worker processes read the same file at startup):
docker compose restart backend search-worker email-worker web-push-worker inbound-email-worker migration-worker
If .env, an environment value, or the Compose service definition changed,
recreate the container instead; docker compose restart preserves the old
container environment:
docker compose up -d --force-recreate backend
If you changed frontend build-time values such as VITE_API_BASE_URL or VITE_DOCS_URL in your deployment platform, rebuild the frontend image as well.
Production baseline
Before production use:
- Replace the default
APP_SECRET. - Use a strong Postgres password.
- Set
session_cookie_secure: trueintow.yamlfor HTTPS. - Configure email delivery in
tow.yamlwith SMTP, file, disabled, or console transport. - Use a durable
TOW_CONFIG_PATHin your deployment configuration. - Preserve
/app/dataor the configured upload and YAML paths. - Confirm
VITE_DOCS_URLpoints to the deployed customer-facing docs, if you want the app to show the Documentation link.