- AI Workspace
- next
- Setting Up
AI Workspace configuration and environment interpolation¶
The AI Workspace stack has two services: the AI Workspace Backend-for-Frontend (BFF) and the Platform API it proxies to. Each reads its configuration from a TOML file (config.toml) layered over built-in defaults.
This page explains how each service loads its config file. It also covers how environment values and mounted files are injected through interpolation tokens, how to keep sensitive values out of the file, and how to provision or rotate the credentials those tokens resolve to. For first-time setup, see Get started with AI Workspace.
How configuration is loaded¶
Each service reads a TOML file mounted into its container, layered over that service's built-in defaults:
- AI Workspace (BFF) —
/etc/ai-workspace/config.toml; every key lives under the[ai_workspace]table. - Platform API —
/etc/platform-api/config.toml; every key lives under the[platform_api]table.
The per-service namespacing ([ai_workspace], [platform_api], [api_portal]) lets one config.toml hold multiple services' sections side by side without their keys colliding—each service reads only its own table.
Environment variables don't override config keys directly
There is no prefix that auto-maps environment variables onto config keys. An environment value reaches a setting only through an explicit interpolation token written into the config file, resolved when the file is loaded. A key written as a plain literal — or absent from the file — ignores the matching variable entirely.
Interpolation tokens¶
Two functions are available inside config.toml:
| Token | Behavior |
|---|---|
{{ env "NAME" "default" }} |
Substitutes the value of environment variable NAME. If the variable is unset or set-but-empty, the default is used. If no default is given, an unset variable fails startup. |
{{ file "PATH" }} |
Reads a secret value from a mounted file at PATH — for injecting secrets from a mounted volume rather than an environment variable. A trailing newline is trimmed. |
An example from the shipped AI Workspace config.toml:
[ai_workspace.control_plane]
url = '{{ env "APIP_AIW_CONTROL_PLANE_URL" "https://platform-api:9243" }}'
[ai_workspace.auth]
mode = '{{ env "APIP_AIW_AUTH_MODE" "basic" }}'
Most tokens in the shipped config carry a default, so an unset variable keeps the built-in value. A token written without a default names a required secret, and startup fails when that variable isn't set.
The variable name is a naming convention, not a prefix override
By convention each token names the key's dotted path, uppercased with dots as underscores, behind a per-service prefix. The prefixes are APIP_AIW_ for AI Workspace, APIP_CP_ for the Platform API, and APIP_AP_ for the API Portal. For example, [ai_workspace.control_plane] url becomes APIP_AIW_CONTROL_PLANE_URL. The loader doesn't interpret the prefix — the name is only the literal string you pass to the interpolation function. You can rename any variable, as long as you edit the matching token in config.toml to agree.
Which variables your deployment reads¶
Because the variable names live in config.toml, the config file is the authoritative list for your deployment — not this page. To see which settings your stack injects from the environment, search the mounted config.toml for {{ env and read the name out of each token.
For every configurable option and the tokens the shipped files carry, refer to the config templates: AI Workspace and Platform API.
Sensitive values in config.toml¶
This section covers credentials the services need to start — database passwords, the OpenID Connect (OIDC) client secret, and the at-rest encryption key. It's a separate mechanism from the AI Workspace secrets feature, which stores encrypted credentials you reference from artifacts.
Never write a sensitive value as a literal in config.toml, and never hardcode one in docker-compose.yaml. Reference each with an interpolation token—from an environment variable or, preferably, from a mounted file:
# Platform API at-rest encryption key - the shipped default reads a mounted file:
encryption_key = '{{ file "/etc/platform-api/keys/encryption.key" }}'
# or, alternatively, from an environment variable:
# encryption_key = '{{ env "APIP_CP_ENCRYPTION_KEY" }}'
# AI Workspace OIDC client secret (oidc mode) - env var, or a mounted file:
client_secret = '{{ env "APIP_AIW_AUTH_OIDC_CLIENT_SECRET" }}'
# client_secret = '{{ file "/secrets/ai-workspace/oidc_client_secret" }}'
Neither encryption_key nor client_secret carries a default, so each is a required secret. Both forms fail closed: if the variable is unset or empty, or the file is missing or outside the allowed source directories, the service refuses to start. A {{ file }} path must live under an allowed directory — /etc/ai-workspace or /secrets/ai-workspace for the BFF, /etc/platform-api or /secrets/platform-api for the Platform API. Override the list with the shared APIP_CONFIG_FILE_SOURCE_ALLOWLIST (comma-separated; it replaces the defaults rather than extending them).
Two unrelated mechanisms
The {{ env }} and {{ file }} tokens on this page are resolved by the service's config loader at startup, and only inside config.toml. The {{ secret "handle" }} placeholder of Secrets management is resolved by the gateway at request time, and only inside artifact configurations. Neither works in the other's place.
Rerun the setup script¶
By default, rerunning ./scripts/setup.sh is safe: it fills in only what's missing and never overwrites a value that already exists. The flags below change that:
| Flag | Effect |
|---|---|
--force |
Regenerate the Transport Layer Security (TLS) certificate, the JSON Web Token (JWT) keypair, and the API Portal session secret, and rotate the admin credentials. Never touches either encryption key. |
--rotate-encryption-key |
Replace resources/keys/encryption.key and resources/keys/api-portal-encryption.key, even though they exist. Destructive. See the warning below. |
--certs-only |
Generate only the TLS certificate. Skips the keys, the admin credentials, and api-platform.env. |
--profiles=<a,b,...> |
Write a different COMPOSE_PROFILES value to .env, for example --profiles=platform-api. Only takes effect if .env doesn't already set COMPOSE_PROFILES, or combined with --force. |
To rotate a single value by hand, delete it from api-platform.env, or delete the file under resources/certificates or resources/keys, and rerun the script. Don't delete resources/keys/encryption.key or resources/keys/api-portal-encryption.key this way. Rerunning the script regenerates a missing encryption key without warning, which makes data encrypted under the old key unreadable. To rotate either encryption key, use --rotate-encryption-key and read the warning below first.
Rotating an encryption key destroys encrypted data
--rotate-encryption-key replaces both encryption keys, which makes everything encrypted under the old keys permanently unreadable. That covers stored AI Workspace secrets and subscription tokens held by the Platform API. It also covers the API Portal's subscription secrets and webhook secrets. At an interactive terminal the script asks you to type rotate to confirm; in a non-interactive run, passing the flag is itself the confirmation. Rotating the JWT keypair with --force is milder. It only invalidates issued login tokens, so everyone signs in again.
Provision the at-rest encryption key manually¶
If you don't run setup.sh, provision the at-rest encryption key yourself before the first start. It protects AI Workspace secrets and subscription tokens, and the Platform API refuses to start if it's missing or malformed. Keep it stable across restarts and replicas.
This covers a self-managed Docker Compose setup. For a virtual machine (VM) or Kubernetes production deployment, where you provision this key alongside the database password and OIDC client secret, see Provision secrets and keys instead.
The key is a single 32-byte Advanced Encryption Standard (AES)-256 value, supplied as 64 hex characters or base64. The container mounts it at /etc/platform-api/keys, and reads it as user ID (UID) 10001, a different user than the one that creates it.
To provision it:
- Generate the key and write it to
resources/keys/encryption.key. -
Grant read access to UID 10001. If your filesystem supports POSIX ACLs, do this without opening the file to every other account on the host:
(umask 077 && openssl rand -hex 32 > resources/keys/encryption.key) setfacl -m u:10001:r resources/keys/encryption.keyIf ACLs aren't available on your filesystem, the fallback is to make the file world-readable instead. This grants read access more broadly than the ACL approach:
Keep the key out of source control, alongside api-platform.env. A trailing newline is trimmed on load. The Platform API doesn't read the key from an environment variable directly. It reads the encryption_key field in config.toml, shown above under Sensitive values in config.toml.
To use the environment variable form instead, switch the token to {{ env "APIP_CP_ENCRYPTION_KEY" }} and set the variable in api-platform.env.
Change environment values after setup¶
api-platform.env holds the values the containers read at startup. Those are the admin credentials the setup script wrote, plus anything else your config.toml pulls in through an {{ env }} token. Edit that file to change a setting, for example to switch the AI Workspace login mode or point at a different control plane. Then restart the stack.
The sample docker-compose.yaml loads the file with the env_file: directive. It sets format: raw so that the $ characters in a bcrypt password hash aren't treated as Compose interpolation:
Keep api-platform.env out of source control. It's git-ignored in the distribution.
Related¶
- Get started with AI Workspace: download AI Workspace, run the setup script for the first time, and deploy the stack
- Change the ports AI Workspace uses: move the stack off its default ports
- Provision secrets and keys: the VM and Kubernetes equivalent of manually provisioning the encryption key, alongside the database password and OIDC client secret
- Troubleshoot AI Workspace: fixes for common setup problems