Menu

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

CommandDescriptionTypical 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 composeConverts 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 docsShows 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

SectionRequiredPurpose
[blueprint]YesDeclares schema, name, and optional vars file.
[vars]NoCompile-time interpolation values for ${vars.KEY} references.
[defaults.component]NoDefaults inherited by components unless overridden.
[defaults.component.logging]NoPersistent logging defaults for components.
[[resources.volumes]]NoDeclares named volumes that components can mount.
[[components]]YesDefines container components MacTain can create and start.

Component Fields

FieldDescriptionExample
idComponent identifier inside the Blueprint.id = "api"
kindComponent kind. Container components use container.kind = "container"
imageOCI image reference.image = "ghcr.io/acme/api:latest"
portsHost-to-container port mappings.ports = [{ host = 8080, to = 8080, type = "tcp" }]
mountsBind, volume, or tmpfs mounts represented in TOML.mounts = [{ volume = "app_data", to = "/data" }]
envRuntime environment variables.env = { NODE_ENV = "production" }
runtime_env_filesUTF-8 env files loaded at runtime.runtime_env_files = ["./env/.env.shared"]
profilesOptional component groups selected with --profile.profiles = ["dev"]
depends_onComponent startup dependencies.depends_on = [{ id = "db", condition = "started" }]
healthHealth check used by dependency conditions and status.health = { type = "tcp", port = 5432, interval = "5s", timeout = "2s", retries = 10 }
network_scopesNetwork scopes for service grouping/discovery.network_scopes = ["my-app"]
secure_egressExisting VPN connection, local-route policy, and optional inbound-port request.secure_egress = { connection_reference = "media-vpn" }
dns, dns_search, dns_optPer-container DNS settings.dns = ["1.1.1.1"]
secretsReferences existing MacTain secrets.secrets = [{ secret = "api-token", env = "API_TOKEN" }]
loggingComponent 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_data and redis_data are 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.
  • postgres owns database-specific env, a secret reference, a volume, and a health check.
  • api depends on postgres and redis, and it repeats ./env/common.env because its component-level runtime_env_files overrides the default array.
  • web is the only always-on component with a host port; internal components do not need host ports just to talk to each other.
  • adminer is tagged with profiles = ["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.toml

Defaults 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.
  • resolve is for reviewing the model MacTain understands after defaults, variables, and profile selection.
  • plan is the preview for runtime changes.
  • start changes 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 into env.
  • 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_on when one component should start after another component is started or healthy. condition = "healthy" requires the dependency component to define a health check.
  • Blueprint files store secret references only, never secret values.
  • Declared volumes are referenced by ID from component mounts.
  • blueprint remove --volumes --yes can 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 dev

Secrets 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.toml

Use 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.toml

Edit 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.toml

Move 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.toml

Use 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.toml

Use 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 --yes

If 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.toml before starting a changed app.
  • Use mactain blueprint resolve ./myapp.toml --json when 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.

Related