Manifest Reference
stack.toml lives in your project root and is meant to be committed — it's the whole "share a stack" file, the non-container equivalent of a Dockerfile. Every field below is optional unless marked required; a bare stack.toml with just a [project] section is valid.
[project]
| Field | Type | Default | Notes |
|---|---|---|---|
| name | string | folder name | Used to key stack status/stack logs/stack down and as the default domain prefix. |
| domain | string | {name}.localhost | .localhost resolves with zero setup in Chrome/Firefox/Edge — no hosts-file edit. Non-browser clients (curl, backend-to-backend calls) fall back to 127.0.0.1:<port>. |
[project] name = "acme-api" domain = "acme-api.localhost"
[[clone]]
Array of tables. Lets a bare stack.toml, dropped in an empty folder, bootstrap the whole project — stack up clones anything missing before doing anything else, and never touches a path that already exists.
| Field | Type | Default | Notes |
|---|---|---|---|
| repo | string | required | Anything git clone accepts. |
| path | string | "." | Where it's cloned to, relative to the project root. |
| ref | string | default branch | Branch, tag, or commit SHA — cloned, then checked out separately (checkout accepts all three; --branch alone doesn't). |
[language.<name>]
Either a bare version string, or a table when you need more control — both forms can coexist as siblings, same as Cargo.toml's [dependencies].
[language] php = "8.3.1" # simple form node = "20.11.0" [language.rust] # detailed form version = "1.75.0" manager = "vfox"
| Field | Type | Default | Notes |
|---|---|---|---|
| version | string | required unless path set | |
| manager | string | inferred | php/node → vfox, python → uv. Any other name needs manager set explicitly. |
| plugin | string | the table key | vfox plugin name override, for the rare mismatch (e.g. Node's plugin is named nodejs). |
| binary | string | {name}.exe | Binary filename override. |
| path | string | — | BYO — a fixed, already-resolved binary. Bypasses vfox/uv entirely. |
Every project pinning the same language+version shares the one binary from vfox's/uv's own store — nothing is copied per project. Activation (see Everyday use) puts it first on PATH the moment you cd in.
[service.<name>]
| Field | Type | Default | Notes |
|---|---|---|---|
| version | string | required | Also the key every other project shares an instance by — same engine+version reuses the one running process. |
| schema | string | project name | Project isolation is by schema/database name, not a separate data directory per project. |
| port | number | engine default | 3306/5432/27017 for mysql/postgres/mongo; required explicitly for any other engine name. |
| command | string | engine default | Built in for mysql/postgres/mongo; required for anything else. {port}/{data_dir}/{path} placeholders available. |
| path | string | — | BYO binary, this project only. Omit to resolve by version via stack register instead. |
| external | bool | false | Adopt an already-running instance — stack only verifies it's listening, never starts/stops it. Errors if path/command are also set, or if nothing's actually listening. |
[service.mysql] version = "8.0.35" schema = "acme_api" # optional — defaults to the project name
[run]
The one process that needs a stable, routed domain — usually your app's own dev server. Omit [run] entirely and stack still activates languages and starts declared services; it just prints that nothing is routed.
| Field | Type | Default | Notes |
|---|---|---|---|
| command | string | required unless external | {port} is substituted; PORT is also set as an env var, for anything reading process.env.PORT. |
| port | number | ephemeral | Pin it if anything (OAuth callback, CORS allowlist, a proxy target) hardcodes the port — stack errors clearly rather than silently picking a different one when it's taken. |
| cwd | string | project root | Which cloned path the command runs in — matters once [[clone]] has more than one entry. |
| external | bool | false | Run the dev server yourself, in your own terminal — stack only validates the port and routes to it, never spawns or tracks the process. port becomes required. See below. |
Why [run].external exists
Sometimes you'd rather run the dev server yourself — watch its own output directly in your own terminal, keep whatever reload workflow you already use, or just not hand the process over to another supervisor. external = true is built for that: stack's only job becomes routing a real domain to the port you tell it about.
[run] external = true port = 8000
[tool.<name>]
| Field | Type | Default | Notes |
|---|---|---|---|
| path | string | — | BYO — any tool you've already installed. Its directory is injected into the [run] process's PATH. |
| version | string | — | For the small set of tools stack knows how to fetch itself (Composer today), or resolved via stack register for anything you've registered globally. |
Command placeholders
Any {VAR_NAME} inside a command string resolves in order: reserved keywords ({port}, {data_dir}, {path}) first, then the process environment, then — only with stack up --prompt — an interactive prompt, masked for anything that looks like a secret. Nothing left unresolved fails silently: every unresolved name is reported together, not one at a time.