pkg/stackfile/schema.json (JSON Schema draft-07). Unknown fields are rejected at parse time — a typo’d key is an error, never silently ignored.
Top-level structure
Resources
Each entry underresources: is one workload. Exactly one of image or build is required.
Workload types
Building from source
branchandtagare mutually exclusive.commitpins a SHA and requiresbranchortagalongside it.- Neither branch nor tag: the server resolves the repository’s default branch.
- Clone credentials come from the preview config or an organization git integration — never from the stackfile.
Ports
Outputs and how to consume them
Three kinds of things produce outputs: resources, addons, and secrets. Each has its own vocabulary and consumption syntax.Resource outputs
A resource’s outputs are derived entirely from itsports:
Naming rule: a resource with one port uses bare names (
port, url, public_url). A resource with multiple ports suffixes with the port name (port.http, url.grpc, public_url.web). host is always bare. Referencing bare url on a multi-port resource is rejected as ambiguous.
Your own outputs — {{ self.X }}
The ref must be the entire value. No templates, no mixing with other refs.
Another resource’s outputs — {{ <resource>.X }}
Exact ref or template. All refs in one value must come from the same source resource.
Addon outputs
Declared under a resource’saddons: block. The addon must already exist in the default project; the block key is its name. Currently supported type: postgres.
Postgres outputs: host, port, database, username, password, sslmode, ca_certificate, url.
Inside the addon’s env:, outputs are referenced bare — no prefix, since the block already names the addon. Templates are fine and may mix any outputs.
{{ postgres.host }} (dotted) is invalid — bare names only, rejected at parse time.
Superuser credentials
superuser: true, {{ username }} / {{ password }} resolve to superuser credentials instead of the app owner role. The addon itself must have been provisioned with enable_superuser_access — the stackfile only requests the connection, it cannot enable it.
Typical pattern: the app connects plain (owner credentials); a dedicated Job resource connects with superuser: true for DDL, extensions, or migrations.
Secret outputs
Each key of a secret in the default project is an output. Consumption is a plain map — no{{ }}, no templating:
Cheat sheet
Volumes
Declare volumes at the top level, mount them per resource:Complete example
Validation summary
Checked at parse time (before anything reaches the server):nameand at least one resource required; unknown fields anywhere are errors.- Exactly one of
image/buildper resource. branchxortag;commitrequires one of them.- Port numbers 1–65535; every port named; max 20 ports, 50 resources, 50 volumes; file size max 1 MiB.
- Volume mounts must reference declared volumes;
depends_onmust reference resources in the file; no self-dependency. - Every
{{ ref }}checked against the source’s real outputs; self refs whole-value only; one source resource per env value; addon refs bare-name only. - Secrets and addons are validated for existence when the stack is resolved server-side.
Example stackfiles
These live inpkg/stackfile/testdata/ and double as test fixtures — every one is validated against the schema and round-tripped through the transpiler on each test run.
Shared database, owner + superuser (superuser_migration.yaml)
One postgres addon, two consumers: the app on owner credentials, a migration Job on superuser credentials.
Everything at once (kitchen_sink.yaml)
Secrets, addon templating, cross-resource refs, self refs, a stateful cache with a volume:
basic_image.yaml (minimal), build_from_source.yaml (git build), with_secrets.yaml, with_addon.yaml, with_addon_superuser.yaml, infisical.yaml (real-world multi-resource app), simple_nginx.yaml.
JSON Schema
Source of truth:pkg/stackfile/schema.json, embedded in the server binary. Point your editor at it (# yaml-language-server: $schema=...) for autocomplete and inline validation.
Not expressible in a stackfile
These exist in the Stack API but have no stackfile syntax (the exporter fails loudly on them rather than dropping them):- Init containers, restart triggers, labels/annotations.
- Private image pull credentials, git integration selection, push targets.
- Building an image from a volume; volume sources (git repo / remote dir / build artifact), storage class, sub-path or read-only mounts.
- Mounting secrets or outputs as files (everything is env).
- Addon provisioning (version, storage, backups, instances) — stackfiles connect to addons, they never create them.