Blueprints
Blueprints are user-authored TOML files that define one or more components and how they start together. The current CLI supports initializing, validating, resolving, planning, starting, stopping, restarting, and checking Blueprint status.
Use Blueprints when a workflow has enough structure that a long container command is no longer the right source of truth: multiple components, repeated ports, persistent data, shared defaults, profiles, DNS settings, or secret references.
Command Reference
| Command | Description | Typical use |
|---|---|---|
mactain blueprint init <path> | Creates a minimal Blueprint file. | Start a new repeatable app definition. |
mactain blueprint validate <path> | Validates Blueprint syntax and supported fields. | Check the file before planning or starting. |
mactain blueprint resolve <path> | Renders the resolved Blueprint model. | Review defaults, profiles, variables, and component output. |
mactain blueprint plan <path> | Previews the operational work MacTain would perform. | Review create/start work before changing runtime state. |
mactain blueprint migrate <path> | Migrates Blueprint v1 to v2. | Update an older Blueprint format. |
mactain blueprint convert compose | Converts Docker Compose YAML to a Blueprint file. | Start a Docker-to-MacTain migration. |
mactain blueprint start <path> | Creates and starts Blueprint components. | Run the app described by the Blueprint. |
mactain blueprint stop <path> | Stops Blueprint components. | Stop a multi-component app without removing definitions. |
mactain blueprint restart <path> | Restarts Blueprint components. | Restart a resolved app workflow. |
mactain blueprint remove <path> | Stops and removes Blueprint components. | Clean up a Blueprint-owned workflow. |
mactain blueprint status <path> | Displays Blueprint component status. | Inspect a running or partially running app. |
mactain blueprint docs | Shows built-in Blueprint examples. | Get installed-release examples from the CLI. |
TOML Structure
MacTain Blueprints are TOML files. Current launch docs target the v2 structure
used by mactain blueprint init by default. The standard structure is:
[blueprint]for identity, schema, and optional vars file.[vars]for compile-time interpolation values.[defaults.component]for fields inherited by every component unless the component overrides that field.[[resources.volumes]]for named volumes declared once at the Blueprint level.[[components]]for each container component in the stack.
[blueprint]
schema = "mactain.blueprint/v2"
name = "my-app"
vars_values_file = "./env/my-app.values"
[vars]
TAG = "latest"
TZ = "UTC"
[defaults.component]
env = { TZ = "${vars.TZ}" }
runtime_env_files = ["./env/.env.shared"]
network_scopes = ["my-app"]
[defaults.component.logging]
enabled = true
max_file_size_bytes = 1000000
retained_file_count = 5
[[resources.volumes]]
id = "app_data"
type = "local"
[[components]]
id = "web"
kind = "container"
image = "docker.io/library/nginx:${vars.TAG}"
ports = [{ host = 8080, to = 80, type = "tcp" }]
mounts = [{ volume = "app_data", to = "/usr/share/nginx/html" }]
profiles = ["dev"]Blueprint Sections
| Section | Required | Purpose |
|---|---|---|
[blueprint] | Yes | Declares schema, name, and optional vars file. |
[vars] | No | Compile-time interpolation values for ${vars.KEY} references. |
[defaults.component] | No | Defaults inherited by components unless overridden. |
[defaults.component.logging] | No | Persistent logging defaults for components. |
[[resources.volumes]] | No | Declares named volumes that components can mount. |
[[components]] | Yes | Defines container components MacTain can create and start. |
Component Fields
| Field | Description | Example |
|---|---|---|
id | Component identifier inside the Blueprint. | id = "api" |
kind | Component kind. Container components use container. | kind = "container" |
image | OCI image reference. | image = "ghcr.io/acme/api:latest" |
ports | Host-to-container port mappings. | ports = [{ host = 8080, to = 8080, type = "tcp" }] |
mounts | Bind, volume, or tmpfs mounts represented in TOML. | mounts = [{ volume = "app_data", to = "/data" }] |
env | Runtime environment variables. | env = { NODE_ENV = "production" } |
runtime_env_files | UTF-8 env files loaded at runtime. | runtime_env_files = ["./env/.env.shared"] |
profiles | Optional component groups selected with --profile. | profiles = ["dev"] |
depends_on | Component startup dependencies. | depends_on = [{ id = "db", condition = "started" }] |
health | Health check used by dependency conditions and status. | health = { type = "tcp", port = 5432, interval = "5s", timeout = "2s", retries = 10 } |
network_scopes | Network scopes for service grouping/discovery. | network_scopes = ["my-app"] |
secure_egress | Existing VPN connection, local-route policy, and optional inbound-port request. | secure_egress = { connection_reference = "media-vpn" } |
dns, dns_search, dns_opt | Per-container DNS settings. | dns = ["1.1.1.1"] |
secrets | References existing MacTain secrets. | secrets = [{ secret = "api-token", env = "API_TOKEN" }] |
logging | Component persistent logging policy. | logging = { enabled = true, max_file_size_bytes = 1000000, retained_file_count = 5 } |
Defaults Vs Component Overrides
Defaults keep repeated settings out of every component. Component-level fields
are for service-specific differences. If a component field is present, it
overrides the same field from [defaults.component]; an explicit empty array
means intentionally none.
This example applies shared runtime env files, network scope, and logging to all components:
[blueprint]
schema = "mactain.blueprint/v2"
name = "defaults-demo"
[defaults.component]
runtime_env_files = ["./env/common.env"]
network_scopes = ["defaults-demo"]
[defaults.component.logging]
enabled = true
max_file_size_bytes = 1000000
retained_file_count = 5
[[components]]
id = "api"
kind = "container"
image = "ghcr.io/acme/api:latest"
[[components]]
id = "worker"
kind = "container"
image = "ghcr.io/acme/worker:latest"This version keeps the same defaults but overrides selected component fields:
[blueprint]
schema = "mactain.blueprint/v2"
name = "override-demo"
[defaults.component]
runtime_env_files = ["./env/common.env"]
network_scopes = ["override-demo"]
[defaults.component.logging]
enabled = true
max_file_size_bytes = 1000000
retained_file_count = 5
[[resources.volumes]]
id = "api_data"
type = "local"
[[components]]
id = "api"
kind = "container"
image = "ghcr.io/acme/api:latest"
# Component-level array fields override defaults, so include common.env here
# when this component needs both common and service-specific env files.
runtime_env_files = ["./env/common.env", "./env/api.env"]
ports = [{ host = 8080, to = 8080, type = "tcp" }]
[[components]]
id = "worker"
kind = "container"
image = "ghcr.io/acme/worker:latest"
# Explicitly disable persistent logging for this component only.
logging = { enabled = false }Compose-Style Stack Example
This example shows a common Docker Compose-style app expressed as MacTain Blueprint TOML: a web frontend, an API, Postgres, Redis, and an optional development-only Adminer UI. It illustrates the standard structure users should copy from when authoring multi-component Blueprints.
[blueprint]
schema = "mactain.blueprint/v2"
name = "todo-stack"
vars_values_file = "./env/todo.values"
[vars]
TAG = "latest"
TZ = "UTC"
[defaults.component]
runtime_env_files = ["./env/common.env"]
network_scopes = ["todo-stack"]
[defaults.component.logging]
enabled = true
max_file_size_bytes = 1000000
retained_file_count = 5
[[resources.volumes]]
id = "postgres_data"
type = "local"
[[resources.volumes]]
id = "redis_data"
type = "local"
[[components]]
id = "postgres"
kind = "container"
image = "docker.io/library/postgres:16"
env = {
POSTGRES_DB = "todo",
POSTGRES_USER = "todo"
}
secrets = [{ secret = "todo-db-password", env = "POSTGRES_PASSWORD" }]
mounts = [{ volume = "postgres_data", to = "/var/lib/postgresql/data" }]
health = { type = "tcp", port = 5432, interval = "5s", timeout = "2s", retries = 10 }
[[components]]
id = "redis"
kind = "container"
image = "docker.io/library/redis:7"
command = ["redis-server"]
args = ["--appendonly", "yes"]
mounts = [{ volume = "redis_data", to = "/data" }]
[[components]]
id = "api"
kind = "container"
image = "ghcr.io/acme/todo-api:${vars.TAG}"
runtime_env_files = ["./env/common.env", "./env/api.env"]
env = {
DATABASE_HOST = "postgres",
DATABASE_NAME = "todo",
DATABASE_USER = "todo",
REDIS_HOST = "redis"
}
secrets = [
{ secret = "todo-db-password", env = "DATABASE_PASSWORD" },
{ secret = "todo-api-token", env = "API_TOKEN" }
]
depends_on = [
{ id = "postgres", condition = "healthy" },
{ id = "redis", condition = "started" }
]
[[components]]
id = "web"
kind = "container"
image = "docker.io/library/nginx:latest"
ports = [{ host = 8080, to = 80, type = "tcp" }]
env = { API_BASE_URL = "http://api:3000" }
mounts = [
{ type = "bind", source = "./nginx/default.conf", to = "/etc/nginx/conf.d/default.conf", read_only = true }
]
depends_on = [{ id = "api", condition = "started" }]
[[components]]
id = "adminer"
kind = "container"
image = "docker.io/library/adminer:latest"
profiles = ["dev"]
ports = [{ host = 8081, to = 8080, type = "tcp" }]
env = { ADMINER_DEFAULT_SERVER = "postgres" }
depends_on = [{ id = "postgres", condition = "healthy" }]The important pieces in this stack:
postgres_dataandredis_dataare declared once in[[resources.volumes]]and mounted by component ID.network_scopes = ["todo-stack"]is a global default, so all components join the same discoverability boundary unless a component overrides it.postgresowns database-specific env, a secret reference, a volume, and a health check.apidepends onpostgresandredis, and it repeats./env/common.envbecause its component-levelruntime_env_filesoverrides the default array.webis the only always-on component with a host port; internal components do not need host ports just to talk to each other.admineris tagged withprofiles = ["dev"], so it only runs when selected with--profile dev.
Create referenced secrets before starting:
printf '%s' "$POSTGRES_PASSWORD" | mactain secret set todo-db-password --stdin
printf '%s' "$API_TOKEN" | mactain secret set todo-api-token --stdin
mactain blueprint validate ./todo-stack.toml
mactain blueprint resolve ./todo-stack.toml
mactain blueprint plan ./todo-stack.toml
mactain blueprint start ./todo-stack.tomlDefaults And Behavior
Use blueprint resolve when you need to inspect the structured model MacTain
understands after defaults and profile selection. Use blueprint plan when you
need an operational preview before create/start work.
- Blueprints are MacTain TOML files, not Docker Compose files.
resolveis for reviewing the model MacTain understands after defaults, variables, and profile selection.planis the preview for runtime changes.startchanges runtime state by creating and starting components.- Profiles let one file represent optional groups such as development-only or production-only components.
[vars]values are compile-time interpolation inputs. They do not become runtime environment variables unless assigned intoenv.- Component fields inherit
[defaults.component]only when omitted. If a component field is present, it overrides the default; an explicit empty array means intentionally none. - Use
ports = [{ host = ..., to = ..., type = "tcp" }]when a component needs to be reachable from the Mac host. Components in the same network scope do not need host ports only to talk to each other. - Use
depends_onwhen one component should start after another component is started or healthy.condition = "healthy"requires the dependency component to define ahealthcheck. - Blueprint files store secret references only, never secret values.
- Declared volumes are referenced by ID from component mounts.
blueprint remove --volumes --yescan delete Blueprint volumes; use it only when you intend to remove local data.
Profiles
Blueprint docs output describes profiles as a runtime selection mechanism.
Components can be tagged with profiles in TOML, then selected with
--profile when resolving, planning, or starting.
Default-profile components are always included. Components tagged with a profile
are included when the matching --profile <name> is passed.
mactain blueprint resolve ./myapp.toml --profile dev
mactain blueprint plan ./myapp.toml --profile dev
mactain blueprint start ./myapp.toml --profile devSecrets And Volumes
Blueprints should reference existing secrets and declared volumes.
[[resources.volumes]]
id = "db_data"
type = "local"
[[components]]
id = "db"
kind = "container"
image = "docker.io/library/postgres:16"
mounts = [{ volume = "db_data", to = "/var/lib/postgresql/data" }]
secrets = [{ secret = "db-password", env = "POSTGRES_PASSWORD" }]printf '%s' "$POSTGRES_PASSWORD" | mactain secret set db-password --stdin
mactain blueprint validate ./db.toml
mactain blueprint plan ./db.tomlUse Secrets for secret command details and Volumes for backup, restore, and volume inspection.
Practical Use Cases
Use case: create a starting Blueprint file
Use this when a single container command is becoming too long or you want a file you can review and share.
mactain blueprint init ./myapp.toml
mactain blueprint validate ./myapp.tomlEdit the generated TOML after initialization, then validate again before planning or starting.
Use case: author a Blueprint from a working container command
Use this when you already have a container command with image, ports, env, and mounts.
mactain container inspect api --json
mactain blueprint init ./api.toml --schema v2
mactain blueprint validate ./api.tomlMove the container settings into [[components]], run validate, then use
resolve and plan before start.
Use case: preview what MacTain will run
Use this before starting a new or changed Blueprint. resolve shows the model
after defaults and profile selection; plan previews operational work.
mactain blueprint resolve ./myapp.toml --json
mactain blueprint plan ./myapp.tomlUse resolve for review and automation. Use plan when you want to see the
create/start work before running it.
Use case: start only development components
Use this when a Blueprint includes optional components for development or production.
mactain blueprint start ./myapp.toml --profile dev
mactain blueprint status ./myapp.tomlUse case: assign one component to Secure Egress
Reference an existing saved VPN connection. Blueprint files store the connection reference and policy, not WireGuard keys or configuration:
[[components]]
id = "qbittorrent"
kind = "container"
image = "lscr.io/linuxserver/qbittorrent:latest"
network_scopes = ["media"]
secure_egress = { connection_reference = "media-vpn", local_route_policy = "network_scopes", inbound_port = { mode = "nat_pmp" } }Use Secure Egress for connection setup, Blueprint Studio navigation, inbound-port choices, and protection checks.
Use case: stop and remove a Blueprint workflow
Use this when you want to stop components and remove their container
definitions. Add --volumes --yes only when you also intend to delete Blueprint
volumes.
mactain blueprint stop ./myapp.toml
mactain blueprint remove ./myapp.toml
mactain blueprint remove ./myapp.toml --volumes --yesIf the TOML file is unavailable, blueprint remove --app-id <id> can remove an
orphaned Blueprint by MacTain Blueprint app ID.
Troubleshooting Entry Points
- Validate the file before starting:
mactain blueprint validate ./myapp.toml. - Use
mactain blueprint plan ./myapp.tomlbefore starting a changed app. - Use
mactain blueprint resolve ./myapp.toml --jsonwhen interpolation, defaults, or profiles do not produce the component shape you expected. - If a secret reference fails, confirm the secret exists with
mactain secret list. - If a component start fails, inspect the related container logs and Troubleshooting.
- If a protected component remains blocked, check the referenced connection and Secure Egress status.
Next Steps
Review Compose Converter if you are migrating an existing Compose file, or continue to Networking and Volumes for operational details.
