Examples

Simple stack

A single top-level key becomes the stack name. Add managed blocks by their stackie.* identifier with empty override objects.

# stackie-stack.yml
my-project:
  blocks:
    stackie.postgres: {}
    stackie.redis: {}
Referenced schema occurrences
  • Managed block mapmy-project.blocks.stackie.postgres
    StackBlockNoderequiredmanymap entry

    Each entry in blocks maps a block instance name to a StackBlockNode.

Stack YAML Format

Reference for Stackie stack YAML files generated from the typed Rust stack contract. YAML is the serialization format; the public API contract is the typed model plus validation semantics.

Contract Metadata

Breaking changes to typed fields, defaults, serialized names, enum variants, or validation semantics require a new stack spec version or an explicit compatibility rule.

Spec version
stackie.stack.v1
Typed contract
bridge::stack::composite::CompositeStack
Owning crate
bridge
YAML serialization
YAML stack files serialize into CompositeStack and are validated before deserialization.
YAML version field
schema_version
Default YAML version
v1
JSON Schema projection
Machine-readable type-shape and editor-assistance projection. Runtime validation uses Bridge validation semantics and the embedded composite stack schema.
Validation rules
4 generated rules
Generated by
stackie gen-yaml-docs

Top-Level Shape

A stack file contains exactly one top-level key. That key becomes CompositeStack.name.

YAML key<stack-name>
Value typeCompositeStack

CompositeStack Fields

A named collection of block instances that run together as a development environment. Stacks are declared in a YAML file with a single top-level key that becomes the stack name.

web-app:
  storage_root: "myapp"
  blocks:
    stackie.postgres:
      stackie.redis:
        stackie.nodejs:
          depends_on:
            - stackie.postgres
            - stackie.redis
#blocks
map<string, StackBlockNode>optional

Map of block node instances indexed by block name.

Keys are block identifiers (e.g., "stackie.postgres"). Values hold dependency declarations and per-instance overrides.

Defaults to an empty map when absent, so substack-only stacks (that only declare stacks:) parse cleanly. The schema layer still enforces that at least one of blocks, ingredients, or stacks is present.

#stacks
map<string, SubstackReference>optional

Substacks to compose into this stack, keyed by stack name.

Each entry references another stack by its name (e.g., stackie.supabase) and optionally provides overrides applied when resolving that substack's blocks.

Defaults to an empty map when the stacks: key is absent, ensuring backwards compatibility with existing stack YAML that has no substack references.

#mode
StackModeoptional

Controls whether automatic port allocation is active for this stack.

  • unmanaged — port numbers must be declared explicitly (default)
  • managed — the port allocator assigns stable port numbers automatically
my-stack:
  mode: managed
  blocks:
    stackie.postgres: {}
#storage_root
string|nulloptional

Optional storage root prefix for all block volume paths in this stack.

When set, every block's named path is stored under ~/.stackie/storage/volumes/{project}/{storage_root}/{path_name}/ instead of the default ~/.stackie/storage/volumes/{project}/{path_name}/.

This lets multiple stacks share the same project namespace without their volumes colliding. Blocks that provide an explicit path override are not affected.

my-stack:
  storage_root: "v2"
  blocks:
    stackie.postgres:  # data at ~/.stackie/storage/volumes/my-stack/v2/data/…
#schema_version
string|nulloptional

Declares the YAML format version for this stack file. Omit to use the default (currently v1). When present, must match a supported version.

my-stack:
  schema_version: v1
  blocks:
    stackie.postgres: {}

Validation Rules

stack.first-party-path· StackBlockNode.path

First-party path validation

Validate all first-party blocks in a composite stack.

For every StackBlockNode with a path field set, this function verifies that:

1. The path is not absolute (only relative paths are permitted). 2. The path resolves to a directory within base_dir after std::fs::canonicalize is applied (prevents symlink-based traversal). 3. The resolved directory exists on the filesystem. 4. The block also has a command set (required for first-party blocks).

Third-party blocks (no path) are skipped entirely.

stack — The parsed composite stack to validate. base_dir — The working directory against which relative paths are resolved. Typically the current working directory (std::env::current_dir()).

Ok(()) if all first-party blocks are valid.

  • StackError::FirstPartyAbsolutePath — path is absolute.
  • StackError::FirstPartyPathTraversal — resolved path escapes base_dir.
  • StackError::FirstPartyPathNotFound — the directory does not exist.
  • StackError::FirstPartyMissingCommand — block has path but no command.

Uses canonicalize + starts_with as the security boundary. String-based .. checks are intentionally omitted — paths like ./sub/../sibling that stay within base_dir are accepted by design.

# use bridge::stack::composite::CompositeStack;
# use bridge::stack::parser::validate_first_party_blocks;
# use std::path::Path;
let yaml = r#"
my-app:
  blocks:
    api:
      path: "./backend"
      command: ["npm", "start"]
"#;
let stack = CompositeStack::from_yaml(yaml).unwrap();
validate_first_party_blocks(&stack, Path::new("/home/user/project")).unwrap();
stack.gateway-block-kind· StackBlockNode

Gateway block validation

Validate per-kind field rules for every block in the stack.

Runs after YAML parsing and schema validation to enforce the discrimination rules described by BlockKind:

- Gateway blocks (gateway.*) must declare a provider: chosen from KNOWN_GATEWAY_PROVIDERS, and must not declare path: or command:. The Gateways router lifecycle is owned by stackied. - Non-gateway blocks must not declare provider: — it is reserved for the gateway kind.

Returns the first error encountered (iteration order is implementation-defined because CompositeStack::blocks is a HashMap).

  • StackError::GatewayMissingProvider
  • StackError::GatewayHasPath
  • StackError::GatewayHasCommand
  • StackError::GatewayUnknownProvider
  • StackError::NonGatewayHasProvider
# use bridge::stack::composite::CompositeStack;
# use bridge::stack::parser::validate_gateway_blocks;
let yaml = r#"
web-app:
  blocks:
    gateway.aws:
      provider: aws
      serves:
        HTTP: { port: 4566, type: api }
"#;
let stack = CompositeStack::from_yaml(yaml).unwrap();
validate_gateway_blocks(&stack).unwrap();
stack.references· CompositeStack.blocks

Block reference validation

Validate stack structure and block references.

Performs validation without checking circular dependencies (handled separately). Checks:

- All third-party block references exist in the provided registry. First-party blocks (those with StackBlockNode::path set) are exempt from the registry check — they are resolved from the local filesystem. - All dependencies reference blocks that exist in this stack.

  • ingredient_exists — Function to check if a block name exists in the registry.

Returns error if any third-party block or dependency is undefined.

validate_first_party_blocks — for path containment and command validation of first-party blocks.

stack.schema· CompositeStack

Schema validation

Validates a parsed YAML serde_yaml::Value against the composite stack schema.

The YAML value is converted to a JSON serde_json::Value first (YAML null/bool/number/string/seq/map all round-trip cleanly for the subset of YAML that stack files use; YAML-only features like explicit tags or non-string mapping keys are rejected with an appropriate error).

Returns StackError::InvalidFormat with a multi-line message listing every schema violation found. The message is formatted for direct display to the user:

Stack YAML does not match schema:
  - /my-stack: missing required property 'blocks'
  - /my-stack/blocks/bad.block/ports/HTTP: 70000 is greater than the maximum of 65535
use bridge::stack::validation::validate_composite_stack_yaml;

let yaml_value: serde_yaml::Value = serde_yaml::from_str(r#"
my-stack:
  blocks:
    stackie.postgres:
"#).unwrap();
assert!(validate_composite_stack_yaml(&yaml_value).is_ok());

Nested Types

#ConsumesEntry

Describes how a block consumes a port served by another block in the stack. Appears in the consumes: map of a block node under a logical name (e.g., DB). The interpolation engine resolves ${consumes.DB} to the actual port number.

String shorthand — the value is used as the block name:

consumes:
  db: neon                     # ${consumes.db.PORT}
  storage: stackie.minio       # ${consumes.storage.API}

Typed struct — provides additional filtering options:

consumes:
  db: { port_type: db }              # match any block with a db-type port
  store: { block_override: stackie.minio }  # explicit block reference
#port_type
string|nulloptional

Filters the consumed port by type (e.g., "db", "web"). When set, only ports matching this type are considered. When omitted, any port from the matched block is accepted.

#block_override
string|nulloptional

Explicitly names the block that provides this port, bypassing automatic resolution. Also set automatically when the string shorthand form is used (e.g., db: neon sets block_override to "neon").

If both block_override and port_type are set, block_override selects the block and port_type filters which port within it is used.

#HealthCheck

Used in full exampleHTTP health check

Health check configuration

Defines how to verify that a sandbox is running and healthy. Used by the daemon to monitor sandbox status.

#interval
integeroptional

How often to perform health checks (seconds) Default: 30 seconds

#timeout
integeroptional

How long to wait for response (seconds) Default: 5 seconds

#retries
integeroptional

Number of consecutive failures before marking unhealthy Default: 3 retries

#start_period
integeroptional

Grace period before starting health checks (seconds) Allows time for sandbox to initialize Default: 0 seconds

#initial_delay
integeroptional

Delay before the first health probe is executed (seconds).

This is distinct from start_period: start_period is Docker's retry accounting grace window, while initial_delay prevents noisy or disruptive probes from being sent before a process can accept them.

Accepted shapes

Execute a command and check exit code Example: { "type": "command", "command": ["pg_isready", "-h", "localhost"] }

required: command, type

Check HTTP endpoint Example: { "type": "http", "url": "http://localhost:8080/health" }

required: type, url

Check TCP port connectivity Example: { "type": "tcp", "port": 5432 } or { "type": "tcp", "port": "${ports.PORT}" }

required: port, type

Check if a process is running by name Example: { "type": "process", "process_name": "postgres" }

required: process_name, type

#PortConfig

Used in full exampleServed API port

Port configuration with number and type

#port
integerrequired

Port number

#type
PortType|nulloptional

Port type classification (optional, defaults to None for backward compatibility)

#expose
booleanoptional

Whether this served port should be shown as a stack-level external link.

#label
string|nulloptional

Optional display label for an exposed external link.

#protocol
string|nulloptional

URL scheme for exposed external links.

#PortType

Port type classification

ValueDescription
"web"

Web service port (HTTP, HTTPS, WebSocket, etc.)

"db"

Database port

"admin"

Administration/management port

"api"

API endpoint port

"metrics"

Metrics/monitoring port (Prometheus, StatsD, etc.)

"grpc"

gRPC service port

"messaging"

Message queue/broker port (AMQP, MQTT, STOMP, etc.)

"misc"

Miscellaneous port

#StackBlockNode

A single block instance within a stack, with optional dependency ordering and per-instance configuration overrides.

Three block kinds are supported based on the block name and fields present:

  • Third-party (stackie.*) — resolved from the ingredient registry; no path: field
  • First-party (any other name) — a locally-owned service; requires path: and command:
  • Gateway (gateway.*) — a cloud provider gateway; requires provider:
my-api: path: "./backend" command: ["npm", "run", "dev"] tools: - node: "20"

stackie.postgres: vars: USER: "mydb"

gateway.aws: provider: aws serves: HTTP: { port: 4566, type: api } consumes: - postgres 
#path
string|nulloptional

Local source directory for first-party (user-owned) blocks. When set, this block is treated as a locally-owned service whose source code lives at this path, relative to the directory where stackie up is run.

Must be a relative path pointing to a subdirectory of the working directory. Absolute paths and .. traversal are not allowed.

When omitted, the block is resolved as a third-party ingredient from the registry.

#depends_on
array<string>optional

Dependencies that must start before this block.

Defaults to empty list if not specified.

#consumes
map<string, ConsumesEntry>optional

Logical port consumption declarations for managed-mode port resolution.

Accepts two YAML forms:

List form (preferred) — each entry is a block instance name. The name is used directly as the interpolation key: F0

Map form — each key is a logical alias for the consumed block: F1

Only meaningful when the parent CompositeStack::mode is StackMode::Managed.

#name
string|nulloptional

Custom display name for this block instance If not specified, defaults to the block type name (e.g., "postgres" from "stackie.postgres") This is similar to docker's --name flag for containers.

#provider
string|nulloptional

Required on gateway.* blocks. Identifies which compiled Gateways provider receives requests for this block.

Accepted values: aws, gcp, azure, cloudflare. The generated Gateway reference page expands the provider table from Gateways runtime metadata, including display names, default ports, and backing-service behavior.

gateway.cloudflare:
  provider: cloudflare
  serves:
    API: { port: 4569, type: api }
#version
string|nulloptional

Override version

#environment
object|nulloptional

Override environment variables

#serves
object|nulloptional

Named port configurations served by this block (accessed as ${serves.NAME}).

Declares the ports that this block exposes to the stack. In managed mode, the port allocator reads these entries to assign stable port numbers from the static-DHCP pool. Port numbers declared here are whitelisted in the sandbox policy.

Accepts ports as a serde alias for backward compatibility: existing YAML files that use ports: continue to parse without modification.

stackie.postgres:
  serves:           # or
  legacy:
    ports:
      PORT: { port: 5432, type: db }
#vars
object|nulloptional

Override string variables (accessed as ${vars.NAME}) These override the default vars defined in the block

#paths
object|nulloptional

Override filesystem paths (accessed as ${paths.NAME}).

Maps a logical path name to the sandbox-side mount path. The physical host directory is derived as: ~/.stackie/storage/volumes/{project}/{path_name}/{sandbox_path_stripped}/

The directory is created automatically before the process starts and the sandbox policy grants read-write access to it.

#hooks_json
anyoptional

Defines commands to run at specific points in the block's lifecycle (e.g., after start, before stop). Hooks are keyed by lifecycle event name.

#init
array|nulloptional

Declarative setup operations (e.g., ensure_dir, copy, write_file) that run after the block's built-in init sequence. Lets stacks provide custom configuration not included in the block definition.

stackie.tomcat:
  init:
    - write_file:
      path: "${paths.base}/conf/server.xml"
      content: |
        <?xml version="1.0" encoding="UTF-8"?> <Server port="-1" shutdown="SHUTDOWN"> ... </Server>
    - ensure_dir: "${paths.base}/logs"
#command
anyoptional

Required for first-party blocks (those with path: set). Specifies the process to run.

Accepts a string (shell-interpreted) or an array of strings (direct exec):

command: "npm run dev"          # shell-parsed
command: ["npm", "run", "dev"]  # direct exec (preferred)
#tools
array|nulloptional

Declares the tools (and versions) that must be available before this block's command runs. The tool manager provisions each tool automatically if not already installed.

Each entry is a single-key map of tool name to version:

tools:
  - node: "20"
  - python: "3.11"
#health_check
HealthCheck|nulloptional

Health check configuration for this block.

Provides (for first-party blocks) or overrides (for third-party blocks) the health check used by the daemon to monitor block availability.

Uses bridge's own HealthCheck type, which supports HTTP, TCP, command, and process-based checks.

#emoji
string|nulloptional

Display emoji for topology view.

Overrides the block's default emoji in the dashboard topology node. For first-party blocks, this is the only way to set a topology icon.

emoji: "🚀" — shown in the topology node for this block instance.

#sandbox
boolean|nulloptional

Controls sandbox isolation for first-party blocks. Defaults to true (sandbox enabled) when omitted. Only applies to first-party blocks; third-party blocks are always sandboxed.

Set to false for development workflows that need unrestricted filesystem access. A warning is shown whenever sandbox isolation is disabled.

#bindings
object|nulloptional

Maps serverless function names to locally-running blocks on gateway.* blocks. When present, the gateway routes invocations to the specified block instead of a container, enabling full IDE debugger attachment without Docker image builds.

gateway.aws:
  provider: aws
  bindings:
    my-lambda-function: my-lambda-block
    another-function: another-block

#StackMode

Controls whether automatic port allocation is active. YAML values: managed or unmanaged.

  • unmanaged — port numbers must be declared explicitly (default)
  • managed — the port allocator assigns stable port numbers automatically
ValueDescription
"managed"

Full managed mode: automatic port allocation.

The port allocator assigns stable ports from the static-DHCP pool to all serves: declarations.

"unmanaged"

No managed-mode automation (the default).

Matches all behaviour prior to managed-mode introduction. Port numbers must be provided explicitly.

#StringOrU16

A value that can be either a string or a u16 (for port numbers that support variable interpolation)

This allows YAML health check ports to be specified as either:

  • Numeric: port: 6379
  • Variable reference: port: "${ports.PORT}"

Accepted shapes

A numeric port value

required: Number

A string that may contain variable references like "${ports.PORT}"

required: String

#SubstackReference

Used in full exampleSubstack reference

Optional overrides applied when composing another stack via the stacks: field. All fields are optional — use {} to include a substack with no overrides.

  • vars — applied to all blocks in the substack
  • ports — port overrides keyed by block name, then port name
  • storage_root — overrides the substack's volume path prefix
stackie.dev:
  stacks:
    stackie.supabase: {}
stackie.redis:
  vars:
    MAX_MEMORY: "512mb"
    ports:
      stackie.redis:
        PORT: { port: 6380 }
        storage_root: "redis-v2"
#vars
object|nulloptional

Variable overrides applied to ALL blocks in the substack (global scope).

These values are merged with each block's own vars, with the override values taking precedence.

#ports
object|nulloptional

Port overrides for blocks within the substack. The outer key is the block name (as declared in the substack), the inner key is the port name. The referenced block must exist in the substack.

#storage_root
string|nulloptional

Overrides the storage root for all blocks resolved from this substack. See storage_root on the top-level stack for full semantics.