Runtime configuration
Configure application containers, platform connections, and environment-specific values.
Use environment variables for infrastructure. Use Cloud settings for product configuration.
For the services and optional integrations needed by each app, start with Deployment requirements.
Cloud validates settings when it reads them. Values stored in Postgres are
encrypted with APP_SECRET.
Set infrastructure variables
| Variable | Purpose |
|---|---|
DATABASE_URL |
Postgres connection used by Bun SQL |
REDIS_URL |
Valkey connection used by Bun Redis for caches and rate limits |
NATS_SERVERS |
Comma-separated NATS JetStream bootstrap URLs |
SYNC_NAMESPACE |
Deployment namespace shared by all application processes |
SYNC_REPLICAS |
JetStream replica count: 1, 3, or 5; default 3. Dev Compose sets 1. Keep consistent across processes; migrate existing resources before changing it. |
NATS_CREDS_FILE |
Optional mounted NATS credentials file |
NATS_TLS_CA_FILE |
Optional trusted CA file for NATS TLS |
NATS_IGNORE_CLUSTER_UPDATES |
Keep reachable seed addresses when advertised Docker hostnames are inaccessible |
NATS_ADMIN_SERVERS |
Gateway Ops only: separate system-account connection for NATS cluster diagnostics |
NATS_ADMIN_CREDS_FILE |
Gateway Ops only: mounted system-account credentials file |
NATS_ADMIN_NKEY_SEED_FILE |
Gateway Ops only: alternative static system-account user seed; mutually exclusive with NATS_ADMIN_CREDS_FILE |
NATS_ADMIN_TLS_CA_FILE |
Gateway Ops only: trusted CA for the separate system connection |
APP_SECRET |
Encrypts settings and credentials |
CLOUD_IDENTITY_KEY_ENCRYPTION_KEY |
Core-only KEK for private platform signing keys; exactly 64 hexadecimal characters |
CLOUD_IDENTITY_NEXT_KEY |
Temporary next Core KEK, distributed before promotion |
CLOUD_IDENTITY_PREVIOUS_KEY |
Temporary previous Core KEK during a rolling rewrap |
CLOUD_OAUTH_BROKER_SECRET |
Shared only by Core and OAuth to authenticate OAuth issuance; exactly 64 hexadecimal characters; required when running OAuth |
CLOUD_IDENTITY_JWKS_ORIGIN |
Optional private transport origin for loading Core's public identity JWKS; does not change the public issuer |
CLOUD_OAUTH_JWKS_ORIGIN |
Optional private transport origin for loading OAuth's public JWKS; does not change the public issuer |
CLOUD_CORE_INTERNAL_ORIGIN |
Private Core origin required for OAuth issuance and workload/mandate broker calls; interactive capability calls can default to the public Cloud origin |
CLOUD_APP_CREDENTIAL |
Per-application resource-bound workload credential for background broker callers; not used by OAuth |
NODE_ENV |
Set to production by built-in images; development tooling supplies its own mode |
ADMIN_LOGIN_TOKEN |
Core-only temporary first-access or recovery token; no production default |
APP_ID selects the application for Cloud's build and development scripts. It
is not application runtime configuration. Runtime identity comes from the app declaration.
Built-in services listen on port 3000 and ignore PORT; application authors can
select an explicit port with app.start({ port }) for standalone deployments.
Every application container must use the same APP_SECRET.
It remains the data/settings encryption input; it never signs or verifies a
session, invocation, or OAuth token.
Only Core receives CLOUD_IDENTITY_KEY_ENCRYPTION_KEY. Other applications
verify browser and invocation JWTs with public keys and must never receive this
secret. Generate it with openssl rand -hex 32 and keep it independent from
APP_SECRET.
Set CLOUD_IDENTITY_JWKS_ORIGIN to Core's private service origin when the
deployment network provides one, for example http://app-core:3000. Tokens
still use the public app.url origin as iss; this variable changes only the
network path used to fetch the purpose-specific session and invocation public
keys. Without it, applications load those JWKS endpoints from the public issuer
origin.
Set CLOUD_OAUTH_JWKS_ORIGIN to the OAuth application's private service
origin, for example http://app-oauth:3000. Access-token verification then
loads /.well-known/jwks.json without public ingress while still requiring the
public app.url issuer. The endpoint publishes only Core's OAuth-purpose public
keys. Applications cache that
public set locally for at most five minutes; the warm verification path uses no
signing-key database query.
Private HTTP origins assume a network that prevents traffic interception and modification, such as a trusted single-host container bridge. Public keys are not secret, but substituted JWKS keys can defeat authentication. Expose only the gateway publicly. Across hosts or untrusted networks, use authenticated HTTPS or an equivalently protected transport for both JWKS and broker traffic. An internal DNS name alone does not provide that protection.
Core and OAuth currently require the same PostgreSQL database for identity state and one-shot grant claims. Separate databases are not supported.
Set CLOUD_CORE_INTERNAL_ORIGIN to the private Core service origin when
applications use workload credentials or mandates. The gateway rejects paths
containing an _internal segment for HTTP and WebSocket requests; background
helpers fail closed when their private origin is missing. Interactive helpers
can still use the public capability route. The helper sends the caller
credential only to Core, where it is resolved once and exchanged for a
target-bound invocation. The target application never receives the source
cookie, OAuth token, or API key.
CLOUD_APP_CREDENTIAL is not shared. Provision a separate resource-bound
service-account credential for each background calling app with resource type
cloud.app, resource ID equal to the app ID, and scope identity:invoke.
OAuth uses its separate deployment broker secret, not a workload credential.
Revoking one workload credential stops only that app's new broker
requests. Never put a workload credential in a browser bundle or target-app
request.
For background callers running directly on the host, set CLOUD_APP_CREDENTIAL and
CLOUD_CORE_INTERNAL_ORIGIN in each calling app's environment. Point the origin
at Core's direct listener, not the gateway. Do not load a shared environment
file containing Core's KEKs into every application.
The repository's development and production Compose files accept
CLOUD_MAIL_APP_CREDENTIAL as an input. They pass it only to Mail as
CLOUD_APP_CREDENTIAL and supply the private service origins. The input name
is not a runtime variable read by Mail.
Keep
APP_SECRETstable and back it up. Cloud has no built-in key rotation; changing it makes encrypted values unreadable. Settings then fall back to their environment value or default; Cloud keeps the stored rows unchanged. See Keep the encryption key stable.
app.start() refuses to boot without APP_SECRET.
Core refuses to initialize identity issuance if its KEK is missing, malformed, or cannot decrypt and validate the active private/public key pairs. Existing applications can continue verifying with public JWKS material and warm caches, but Core fails closed for new issuance.
Start Core before its consumers. Every replica behind the configured Core origin must serve the session and invocation JWKS endpoints. Verify cold JWKS reads as well as cached requests.
Browser sessions and internal invocations use signed JWTs. An invocation is valid for one target and operation for 30 seconds, with two seconds of clock tolerance. Scheduled work uses mandates, never a stored browser session.
Synchronize every host's clock, for example with NTP, and monitor drift well within the invocation verifier's two-second tolerance.
OAuth always uses Core issuance. Generate CLOUD_OAUTH_BROKER_SECRET once with
openssl rand -hex 32 and inject the same value exclusively into Core and OAuth.
Do not reuse APP_SECRET or Core's KEK. This secret authenticates only OAuth's
closed broker endpoints; Core still checks the current grant, client and
principal before signing. No admin login or credential provisioning is needed.
Development Compose supplies a public, development-only default. Production Compose requires an explicit value. Start Core before OAuth. OAuth checks the secret and Core signer at startup, before migrations, and fails closed if either is unavailable. Missing or malformed Core configuration disables the OAuth broker but does not prevent running Core without OAuth.
To rotate the broker secret, pause OAuth traffic, replace the value on all Core and OAuth replicas, recreate those containers, and verify OAuth readiness before resuming traffic. There is no previous-secret overlap; mismatched replicas reject issuance. Rotation alone does not invalidate existing JWTs or refresh grants.
Before upgrading mandate writers, follow the coordinated schema cutover in Deprecations and migrations.
Mail incoming automations use mandates. Provision Mail's identity:invoke
app credential and keep Core's broker available. For upgrades that change
stored credentials, follow Deprecations and migrations.
For first access, temporarily provide ADMIN_LOGIN_TOKEN only to Core and open
/auth/login?method=admin. Set up and verify normal administrator sign-in,
then remove the token and recreate Core. See the
fresh installation sequence.
Use settings for application values
Declare settings with defineApp({ settings }).
The runtime reads them from the shared store, validates them, and decrypts secrets. A write invalidates the shared cache so other containers see the new value on their next read.
Use environment fallbacks only for first deployment or infrastructure-managed values. The setting remains the canonical product configuration.
See Settings for declaration and request access.
Set the public URL
Set app.url to the public base URL.
It is used for email links, OAuth redirects, WebAuthn, and other absolute URLs. Use HTTPS outside localhost.
APP_URL can bootstrap this setting.
Configure optional services
Applications may declare settings for services such as:
- FreeIPA;
- Filegate;
- mail providers;
- AI providers;
- PDF rendering.
GOTENBERG_URL can bootstrap the PDF rendering setting gotenberg.url; the
development stack sets it to its Gotenberg container. FreeIPA and Files use
administration settings only. Mailbox credentials belong
to user-created IMAP/SMTP connections, not global provider settings. Grids query
limits are configured in Grids administration and require restarting every Grids replica.
Liquid templates now stop during rendering when they exceed their time or
output budget. The shared defaults are 1,000 ms and 300,000 UTF-8 bytes;
application-specific output caps still apply. A template preview or send can
fail with render_timeout, render_too_large, or render_memory_limit.
Grids HTML cells share 2 seconds of template rendering per read or export;
Mail signature segments share 1 second per composed body.
Simplify an affected template or reduce its data. These budgets are application
API options, not deployment settings; see
PDF and templates.
Control CLI plugin access
Applications serve their cld command modules as plugins. Every plugin
request needs a signed-in session, an OAuth token, or an API key; signed-out
requests get neither the plugin list nor any plugin file.
The setting cli.plugins.access decides who else is excluded. It applies to
the list and to every download:
| Value | Who gets plugins |
|---|---|
full_users (default) |
Full accounts and service accounts. Guests get 403. |
all_users |
Every signed-in account, guests included. |
Change it under Administration › Settings › Command line. The setting only
protects the plugin code; the server still authorizes every command a plugin
sends. Users install the plugins with cld plugins install <name> or accept the
offer after cld login; cld verifies every file against the served manifest. See Application CLI modules.
Offer the OpenAI-compatible API
In AI settings, enable ai.openai_api_enabled (OpenAI-compatible API) to
let people create models-only personal API keys and use their allowed Assistant
chat models from external clients. It is off by default. Every request checks
the switch, so turning it off stops new requests with existing keys too;
provider calls already running continue.
ai.openai_api_max_concurrent_requests defaults to 4 simultaneous requests
per person across all instances (integer, 1–64). Typical clients need one or two;
four leaves room for a second client without one script crowding out others.
Raise it when provider capacity supports more simultaneous requests.
Models without reference prices have no usage limit here, as in Assistant. Chat budgets do not protect them; only the IP rate limit and per-person concurrency limit apply. Review model prices before enabling this surface. See the client reference for authentication, supported fields and errors.
Validate a deployment
Check configuration in this order:
- the container received the expected variables;
- Postgres, Valkey and NATS names resolve on the private network;
- application containers share
APP_SECRET; the gateway does not receive it; - only Core has the current identity KEK;
- only Core and OAuth share the OAuth broker secret, and background callers have only their own scoped workload credentials;
app.urlmatches the public origin;- required settings validate in the administration UI;
- the application starts without fallback warnings.
Do not print secrets while diagnosing configuration.