Skip to content

minato.toml

Lives at the repository root and is committed. Every worktree reads the same one.

toml
[project]
name = "myapp"
# domain = "myapp.localhost"

[runtime]
default = "docker"

[services.web]
image = "node:22"
port = 3000
command = "npm run dev"
health = "http://localhost:3000/healthz"
idle_timeout = "30m"
depends_on = ["db"]
env = { NODE_ENV = "development" }

[services.db]
image = "postgres:16"
port = 5432
scope = "project"
expose = false
volumes = ["pgdata:/var/lib/postgresql/data"]

[project]

KeyType
namestringrequiredAppears in every URL. Must be unique across the projects one daemon manages
domainstring{name}.localhostThe URL suffix. Anything other than .localhost needs its own /etc/resolver entry

Registering two projects under one name is refused rather than allowed to collide.

[runtime]

KeyType
defaultstring"docker""docker" or "apple"

See Runtimes.

[services.<name>]

The service name appears in URLs and in MINATO_URL_<SERVICE>, so keep it to letters, digits and -.

Image and command

KeyType
imagestringone of theseA prebuilt image. postgres:16, docker.io/library/node:22
buildstringone of theseA build context, relative to the worktree. Mutually exclusive with image
dockerfilestring{build}/DockerfileThe Dockerfile, relative to the worktree. Needs build
build_argstable{}--build-arg values. Needs build
commandstringimage defaultReplaces the image's command. Parsed shell-style, so quotes group arguments
workdirstring/workspaceWorking directory inside the container

Your worktree is mounted at /workspace, which is why that is the default.

Networking

KeyType
portintegerThe port the app listens on inside the container
exposebooleantrue when port is setWhether to give it a URL

There is no host port to configure. Docker forwards to a port it chooses; Apple Container gives the container its own IP.

Bind 0.0.0.0 inside the container, not 127.0.0.1 — a server on loopback inside a container is unreachable from outside it.

Building

toml
[services.web]
build = "."
dockerfile = "./docker/web.Dockerfile"   # optional
build_args = { NODE_VERSION = "22" }     # optional
port = 3000

The context comes from the worktree, not the main checkout, so a branch that edits its Dockerfile gets the image that Dockerfile describes. It has to stay inside the worktree; build = "../.." is refused rather than handed to the runtime as a build context.

The image is tagged minato-{project}-{service}:{fingerprint}, where the fingerprint covers the Dockerfile and the build args. Two consequences worth knowing:

  • Two worktrees whose Dockerfiles agree land on the same tag and share one image, built once.
  • A build is skipped when that tag already exists. This is what keeps waking a stopped service from running a build.

A copied file does not trigger a rebuild

The fingerprint cannot see files the Dockerfile COPYs in, so editing package.json alone does not cause a rebuild. Use minato up --build. docker compose behaves the same way.

Readiness

KeyType
healthstringTCP connectHow to decide the service is ready
toml
health = "http://localhost:3000/healthz"   # 2xx or 3xx
health = "tcp://localhost:5432"            # a connection succeeds
health = "cmd:pg_isready -U postgres"      # runs inside the container

Only the path is used for http://. What you write is the address from inside the container; Minato reaches it at whatever address the runtime assigned.

Lifecycle

KeyType
idle_timeoutduration"30m"Time without a request before it stops itself
depends_onarray[]Services to start first
scopestring"workspace""workspace" or "project"

Durations are humantime: "30s", "10m", "2h".

depends_on sets order. On Apple Container it also decides whether MINATO_HOST_<PEER> is available, since the address is read when the service starts.

scope = "project" shares one instance across every worktree. Good for a database you do not want to seed repeatedly; bad when two branches carry incompatible migrations.

Storage

KeyType
volumesarray[]Mounts
toml
volumes = [
  "pgdata:/var/lib/postgresql/data",   # named, managed, shared across worktrees
  "./seed:/seed",                      # host path, relative to the worktree
  "/etc/ssl/certs:/certs:ro",          # absolute, read-only
  "~/.cache/npm:/root/.npm",           # home-relative
]

A source with no / is named storage; anything starting with /, ./ or ~/ is a host path. A :ro or :rw suffix sets the mode, defaulting to read-write. The container path must be absolute.

Apple Container has no named volumes, so they become bind mounts under ~/.minato/volumes/<project>/.

Environment

KeyType
envtable{}Variables for this service
toml
env = { NODE_ENV = "development", PORT = "3000" }

This is the project layer, and it is committed — keep secrets out of it. See Environment variables.

Validation

console
$ minato status
✗ error: invalid configuration: service `web`: depends_on names an unknown service `database`

The configuration is checked when it is read. Unknown service references, circular depends_on, malformed volumes and invalid durations are all caught before anything starts.

Released under the MIT License.