scaledbox.toml reference
A product describes itself in scaledbox.toml at the root of its repository: its services, the
resources every environment gets, and what differs per environment. Unknown settings are refused.
Sizes are Kubernetes quantities (256Mi, 1Gi), CPU in cores or millicores (100m),
durations Go's (60s, 15m).
[app]
The product.
| Setting | Type | Description |
|---|---|---|
name |
string | The product's name, as approved: lower-case letters, digits and single dashes, at most 30 characters |
[services.<service>]
A service: a box app on scaledbox's runtime (JavaScript modules your build made, Workers-shaped handlers, and its static assets), answering at its domains if it has any, else a worker (names: lower-case letters, digits and single dashes).
| Setting | Type | Description |
|---|---|---|
dir |
string | Where its build put it, relative to scaledbox.toml (default: the service's name): its box.toml (main: its entry module; bindings, fetched hosts, routes, cron, limits) and its JavaScript modules (.js, .mjs, each with its source map .map); scaledbox deploy sends them |
assets |
string | Its static files' directory, relative to scaledbox.toml (none: no assets): served at its names beside its fetch (box.toml's [assets] says which paths its fetch answers) |
domains |
array of strings | Its public names, over HTTPS: the product's own domains (e.g. example.com, staging.example.com), each answering once its records hold (scaledbox domains) |
health |
string | An HTTP path its fetch answers 2xx at when it works: a box of each new release is asked it before the release goes live, and it is its names' uptime check |
replicas |
integer | A fixed number of boxes, all kept awake, 1 to 50 (default 1): the same as min_replicas and max_replicas both set to it |
min_replicas |
integer | Boxes kept awake, 0 to 50 (default 1): never asleep, their top level and timers running; 0: it sleeps when idle and wakes on its next request, cron tick or queue delivery, as previews do |
max_replicas |
integer | The most boxes, 1 to 50 (default: min_replicas): a request goes to the awake box with the fewest in flight, and spills over to the next once each has 20, asleep again when idle. Billed as reserved at max_replicas, so a range within it admits nothing more |
memory |
string | Each box's memory and its cap, e.g. 512Mi (default 256Mi, 32Mi to 2Gi); billed as reserved, for each of its max_replicas |
env |
table of strings | Plain variables, each a string in env (env.LOG_LEVEL); secrets are set with scaledbox secrets set, never here, and are strings in env too |
[postgres.<database>]
A database and its role in every environment, on the product's own Postgres instance: the services' database bindings (and kv, queues, workflows, objects) reach main; others are reached by tools (names: lower-case letters and digits, at most 10). Removing it never drops it.
| Setting | Type | Description |
|---|---|---|
size |
string | Its size, reserved and billed (the product's Postgres instance's disk is sized to hold every environment's): beyond it, it turns read-only (default 1Gi) |
[bucket]
Object storage in every environment: its own prefix of the product's bucket at scaledbox's S3 gateway, with its own keys; the services' bucket bindings reach it (R2's API); presigned URLs work.
| Setting | Type | Description |
|---|---|---|
public |
boolean | Anyone may read its objects (public-read); writing needs its keys |
domain |
string | A public bucket's own name, e.g. files.example.com: GET https://<domain>/<key> reads its object <key>, through Cloudflare's CDN; proven as the product's other domains (scaledbox domains shows its records) |
size |
string | Its size, reserved and billed: beyond it, nothing more can be written (default 5Gi) |
max_object |
string | Its largest object, e.g. 50M (default 25M, at most 100M: 100,000,000 bytes, Cloudflare's request body): a larger upload, multipart part or multipart total is refused (400 EntityTooLarge) |
[release]
Before each release goes live, a service's release handler runs in a box of the new release (e.g. the database's migrations), within its duration: if it fails, nothing changes.
| Setting | Type | Description |
|---|---|---|
service |
string | The service whose release handler runs: its default export's release(event, env), event { environment, release, first, preview } (first: the environment's first release, e.g. a preview's sample data then) |
[env.<environment>]
What differs in one environment (e.g. prod, staging).
[env.<environment>.services.<service>]
Settings of [services.<service>] for this environment only: each one given here replaces the shared one (env variables are merged by name).
[previews]
Previews of pull requests (scaledbox deploy --env pr-<number> --preview): one service at pr-<number>.<domain>, every service asleep when idle, with databases of their own (empty, or copies of another environment's: branch_from), deleted with the environment or after a week without a deploy.
| Setting | Type | Description |
|---|---|---|
service |
string | The service that answers, at pr-<number>.<domain>; the others run without a name |
domain |
string | One of the product's own domains, a zone's apex (previews are one level below it, as Cloudflare's free certificate covers); its DNS points *.<domain>, or each preview's name, at the product's target |
branch_from |
string | An environment (e.g. staging) each preview's databases are copies of, as they are when the preview is first deployed: each [postgres.<name>] that environment has, its files cloned in a fraction of a second on a data node's ZFS pool (its sessions end for about 0.1 s; its apps' pools reconnect). A database it lacks, or one on an instance not yet on a pool, starts empty. Without it, previews' databases start empty (the release handler's first release fills them: event.first) |
[budget]
The most the product means to spend a month: its limit is the lower of this and the credit scaledbox gives it.
| Setting | Type | Description |
|---|---|---|
usd_month |
number | USD a month (more than 0) |
[tenants]
Where the product's tenants' apps answer.
| Setting | Type | Description |
|---|---|---|
domain |
string | One of the product's own domains: its tenants' apps answer at <subdomain>.<domain> once its wildcard and ACME records hold (scaledbox domains) |
[status]
The product's public status page, served by its cluster: its environments' names, whether each answers and its uptime over 24 hours, 7 and 30 days.
| Setting | Type | Description |
|---|---|---|
domain |
string | Its name, one of the product's own domains (e.g. status.example.com), proven like the others (scaledbox domains) |
environment |
string | The environment whose deploys claim and route it (default prod) |
[notify]
Where its environments' deploys are announced: each one's start, success, failure and rollback (previews' aren't: their pull requests' checks are).
| Setting | Type | Description |
|---|---|---|
chat |
string | The name of the environment's secret holding a Slack incoming webhook's URL (a Discord webhook's and an ntfy topic's work too): its deploys are posted there. Set it in each environment announced (scaledbox secrets set --env prod DEPLOYS_SLACK, its value the webhook's URL): a deploy of an environment without it is refused |
webhook |
boolean | Also send them to the product's webhook (PUT /v1/projects/{project}/webhook) as deploy.started, deploy.succeeded, deploy.failed and deploy.rolled_back events, signed and retried as its others (Standard Webhooks) |
[ai]
The product's AI gateway: its tenants' apps call LLMs through their ai binding (env.AI) with the product's own provider keys, and hand a long answer's stream to their client while they sleep; always on, its memory reserved and billed (in one environment, default prod).
| Setting | Type | Description |
|---|---|---|
domain |
string | Its name, one level below the product's tenant domain (e.g. ai.example.com when [tenants] domain is example.com), under its wildcard record and certificate: the URLs a box hands its clients a stream at |
memory |
string | Memory reserved for it and its limit, e.g. 256Mi (default 128Mi, 64Mi to 4Gi), billed as a service's: it holds about 70 KiB a stream under way beside its ~12 MiB |
environment |
string | The environment whose deploys run it and whose secrets hold its keys (default prod) |
[ai.providers.<provider>]
Its providers by name (lower-case letters, digits and dashes): tenants name a model <provider>/<model>, e.g. anthropic/claude-sonnet-4-5.
| Setting | Type | Description |
|---|---|---|
api |
string | The API it speaks: openai-completions (OpenAI's Chat Completions, and OpenAI-compatible providers), openai-responses (OpenAI's Responses), anthropic-messages (Anthropic's Messages) or google-generative-ai (Google's Gemini API) |
base_url |
string | Its API's base (https), e.g. an OpenAI-compatible provider's, OpenRouter's or Groq's (default: OpenAI's, Anthropic's or Google's own) |
key |
string | The name of the environment's secret holding the product's API key for it (scaledbox secrets set --env prod OPENAI_KEY): the gateway calls it with that key; tenants never see it |