An optional file at the root of the repository that tells the deployer how to run your compose project: which service gets the traffic, how it is health-checked and switched, and what runs before a release. Unknown keys are errors (with line and column), so typos never pass silently.
The smallest useful file
[web]
service = "app"
port = 8080
healthcheck = "/up"
Without the file, a single label deployer.expose: "8080" on the web service does the same
([web] service + port, health check /). If both exist and disagree, the deploy fails with a clear error.
Every key
# deployer.toml at the root of the repository. Every key is optional;
# [web] is required unless a compose service has the label deployer.expose.
version = 1
compose = "compose.yaml" # only needed to override the auto-detection
[web]
service = "app" # the compose service that receives HTTP traffic
port = 8080 # its plain HTTP port inside the container
healthcheck = "/up" # GET this path; 2xx/3xx = healthy
healthcheck_timeout = "3s" # per request
deploy_timeout = "60s" # start -> healthy
drain_timeout = "30s" # the old version keeps running for open requests
stop_timeout = "10s" # SIGTERM -> SIGKILL
strategy = "bluegreen" # or "recreate" (stop old, then start new)
[release] # runs once in the NEW image before traffic moves
command = ["bin/rails", "db:migrate"]
timeout = "10m"
[build]
pull = "newer" # missing | newer | always (base images)
secrets = ["NPM_TOKEN"] # env vars flagged "build", for RUN --mount=type=secret,id=NPM_TOKEN
[services.worker]
strategy = "recreate" # default for services with build:
[services.redis]
strategy = "keep" # default for image-only services
| Key | Default | Allowed | Meaning |
|---|---|---|---|
version | 1 | 1 | Schema version; other values are rejected. |
compose | auto-detected | a path in the repository | Use another compose file, e.g. deploy/compose.prod.yaml. |
profiles | none | compose profile names | Services with a profile run only if it is listed here. |
[web] service | from the label | a compose service | The service that receives HTTP traffic. It runs in two slots for zero-downtime switches. |
[web] port | from the label | 1–65535 | Its plain HTTP port inside the container. ports: in compose are ignored. |
[web] healthcheck | / (any answer below 500) | a path | Probed before traffic moves; with a path, only 2xx/3xx count. More. |
[web] healthcheck_timeout | 3s | 1s – 30s | Per probe request. |
[web] deploy_timeout | 1m | 10s – 15m | From container start until healthy; otherwise the deploy fails and the old version keeps serving. |
[web] drain_timeout | 30s | 0s – 10m | How long the old version keeps running after the switch, for open requests. |
[web] stop_timeout | compose stop_grace_period, else 10s | 1s – 2m | Between SIGTERM and SIGKILL. |
[web] strategy | bluegreen | bluegreen, recreate | recreate stops the old version before starting the new one (short downtime) for apps that must never run twice. |
[release] command | none | a command list | Runs once in the new image before traffic moves (migrations). A failure stops the deploy. |
[release] timeout | 10m | 1m – 1h | |
[build] pull | newer | missing, newer, always | When base images are pulled. newer picks up a patched node:22 on the next deploy. |
[build] secrets | none | variable names | Variables flagged build that the build may read with RUN --mount=type=secret,id=NAME. Never use build args for secrets: they stay in the image. |
[services.NAME] strategy | recreate with build:, keep otherwise | recreate, keep | recreate: restarted on the new image after a successful switch. keep: left running unless its definition changes (databases). |
[services.NAME] no_new_privileges | true | true, false | Opt a service out of the no-new-privileges hardening (only if it needs setuid binaries). |
Durations are written like 90s, 5m, 1m30s. The file may be at most 64 KiB.
What is deliberately not in this file
Domains, environment variables and secrets, the branch, memory/CPU limits and release retention are set in the deployer, not in the repository: anyone with push access must not be able to claim a domain, read secrets or give the project more resources.