Configuration
The control plane settings in .env, and the runtime settings you change through the admin API.
The control plane reads its settings from infra/compose/.env. setup.sh writes a short one, and every setting is documented in env.example next to it.
Change a setting
Edit .env, or re-run setup.sh with the flag, then recreate the containers.
# on the control plane, in platinum/infra/compose
docker compose up -dRe-running setup.sh keeps every value you do not pass, and never rotates a secret.
Know the derived values
Compose derives these, so .env stays short. Set one only to override it.
| Setting | Derived as |
|---|---|
PT_PUBLIC_API_URL | https://<PT_DOMAIN>. The URL clients, SDKs and hosts use. Baked into every install command. |
PT_AUTH_BASE_URL | The API URL. Used for sign-in callbacks and session cookies. |
PT_API_HOST | <PT_DOMAIN> |
PT_PUBLIC_APP_ORIGIN | https://<PT_CONSOLE_DOMAIN>, else the API URL. Used in emails and redirects. |
PT_POSTGRES_BIND, PT_S3_BIND | PT_PRIVATE_IP, else 127.0.0.1 |
PT_CP_ADVERTISE_IP | PT_PRIVATE_IP. Hosts accept :18048 only from it. |
PT_EDGE_DATABASE_URL | Postgres at PT_PRIVATE_IP. Given to each host's edge proxy. |
PT_S3_ENDPOINT | http://<PT_PRIVATE_IP>:<PT_S3_PORT>, the bundled store |
Set endpoints and images
| Setting | Default | Meaning |
|---|---|---|
PT_DOMAIN | none | The API hostname Caddy serves, or an http:// address on a private network |
PT_ACME_EMAIL | empty | Optional Let's Encrypt contact |
PT_PRIVATE_IP | detected | The address sandbox hosts reach this machine on |
PT_SBX_DOMAIN | empty | Preview URLs become <port>-<sandbox>.<PT_SBX_DOMAIN> |
PT_SBX_PUBLIC_PORT | 443 | The port in preview URLs |
PT_CONSOLE_DOMAIN | empty | The hostname the console is served on |
PLATINUM_VERSION | none | The image tag both images run at |
PT_API_IMAGE, PT_WEB_IMAGE | GHCR | Pull from a mirror with the same tags |
COMPOSE_PROFILES | s3 | Optional services: s3 (bundled store), console |
PT_POSTGRES_PORT | 5432 | Host port Postgres is published on |
PT_HTTP_PORT, PT_HTTPS_PORT | 80, 443 | Host ports Caddy publishes |
PT_DEFAULT_IMAGE | alpine:3.20 | Image a create with no template builds, until pt-base exists |
Keep the secrets
setup.sh generates all of these once. Back them up with .env.
| Setting | Used for |
|---|---|
POSTGRES_PASSWORD | The database. Hex only: it is embedded in a URL. |
ADMIN_TOKEN | Super-admin bearer: mints install commands, manages hosts and config. |
EDGE_HMAC_SECRET | Signs preview-URL tokens. Copied to every host's edge proxy. |
EDGE_HINT_SECRET | Signs the route hints the control plane forwards to host edge proxies. |
PT_BAUTH_SECRET | Session signing. |
PT_SIG_PRIVATE_KEY | Signs snapshots. A new key invalidates every snapshot signed with the old one. |
PT_CACHE_GRANT_PRIVATE_KEY, PT_CACHE_GRANT_ACTIVE_KID | Signs volume gateway grants. Unset, pt vol ls and pt vol cp are refused. |
PT_SECRETS_KEY | Encrypts stored credentials. |
PT_SECRETS_ACTIVE_KID, PT_SECRETS_KEY_<KID> | The org-secret keyring. Keep old kids when you rotate. |
PT_VSOCK_PROXY_TOKEN | Bearer for the control channel to each host's :18048. Copied by the installer. |
Control accounts and sign-in
| Setting | Default | Meaning |
|---|---|---|
PT_PRODUCTION | 1 | Refuse to start with a development default secret. Keep it on. |
PT_ADMIN_EMAILS | your --email | Can always sign up; become platform admins once the address is verified |
PT_SIGNUP_MODE | invite-only | open, invite-only or closed |
PT_SIGNUP_DOMAIN_ALLOWLIST | empty | In open mode, only these email domains may sign up |
PT_ADMIN_IP_ALLOWLIST | empty | Restrict /_admin/* and /internal/admin/* to these IPs or CIDRs |
PT_AUTH_TRUSTED_ORIGINS | empty | Extra browser origins allowed to call /api/auth/* |
PT_DASHBOARD_ORIGINS | empty | Origins treated as first-party dashboards |
PT_COOKIE_DOMAIN | set by setup.sh | Shares the session cookie across subdomains, such as .example.com |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | empty | Google sign-in |
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET | empty | GitHub sign-in |
Send email
Without a provider, the server logs each message's subject and recipient and sends nothing.
| Setting | Meaning |
|---|---|
RESEND_API_KEY | Send through Resend |
MAILTRAP_API_TOKEN | Send through Mailtrap |
EMAIL_FROM_EMAIL, EMAIL_FROM_NAME | The sender |
Connect object storage
| Setting | Default | Meaning |
|---|---|---|
PT_S3_ENDPOINT | the bundled store | host[:port] (HTTPS) or a full http(s)://host[:port] URL |
PT_S3_REGION | us-east-1 | |
PT_S3_BUCKET | platinum | |
PT_S3_PREFIX | empty | Key prefix inside the bucket |
PT_S3_ACCESS_KEY, PT_S3_SECRET_KEY | generated for the bundled store | |
PT_S3_PORT | 9000 | Port the bundled store is published on |
PT_CAS_GC_IN_PROCESS | 1 | Runs the nightly storage garbage collection inside the server |
The installer copies the PT_S3_* values to each host. See Object storage.
Change runtime settings
Some settings live in the database and change live, with no restart. Set one with the admin token.
curl -s -X PUT https://api.example.com/internal/admin/config/flags.volumes_enabled \
-H "Authorization: Bearer $ADMIN_TOKEN" -H 'content-type: application/json' \
-d '{"value":true}'DELETE on the same path returns a key to its default.
| Key | Default | Meaning |
|---|---|---|
flags.volumes_enabled | false | Persistent volumes. Needs object storage. |
flags.vol_v1_enabled | false | The current volume format. Needs flags.volumes_enabled. |
placement.regions | eu-west, us-east | Map of public region to the datacenter names hosts register with |
overcommit.ram_alloc_ratio | 0.8 | Share of a host's RAM that sandboxes may be allocated |
overcommit.host_cpu_refuse_pct | 85 | No new sandboxes on a host above this measured CPU use |
placement.regions replaces the whole map, so include every region you keep:
curl -s -X PUT https://api.example.com/internal/admin/config/placement.regions \
-H "Authorization: Bearer $ADMIN_TOKEN" -H 'content-type: application/json' \
-d '{"value":{"eu-west":{"label":"EU West","datacenters":["eu-west"]},
"lab":{"label":"Lab","datacenters":["lab"]}}}'A fresh install with no STRIPE_SECRET_KEY starts with billing off.