Appearance
Services
Every entry under [services] is one service. The name is also its DNS name inside the project, so it follows DNS label rules: lowercase letters, digits and -, starting with a letter, at most 32 characters. Services and databases share one namespace.
Types
type | What it is | Needs |
|---|---|---|
web (default) | A long-running process that opens a port | port |
worker | A long-running process with no port and no domain | — |
cron | A one-shot container per schedule trigger | schedule |
toml
[services.web]
source = { path = "." }
port = 8080
[services.queue]
type = "worker"
source = { image_of = "web" }
command = ["bin/worker"]Cron jobs have their own page: Cron jobs.
Source
source says where the image comes from. Exactly one form:
| Form | Example | Notes |
|---|---|---|
path | { path = "api", dockerfile = "Dockerfile" } | Build from this repository. path is the build context; both default to . and Dockerfile. |
repo | { repo = "acme/billing", branch = "main", path = "." } | Build from another repository the org can reach. |
image | { image = "redis:7-alpine" } | Pull a public image. No build. |
image_of | { image_of = "web" } | Run the image another service builds. One build, many processes. |
Paths must stay inside the repository: no absolute paths, no ...
image_of cannot point at itself or at a service that itself uses image_of.
Build args
toml
[services.web.source]
path = "."
build_args = { NODE_ENV = "production", NPM_TOKEN = { secret = true } }A plain value is passed as --build-arg and is visible in the image history. A secret value is passed as a BuildKit secret and never lands in a layer. Read it in the Dockerfile with a secret mount:
dockerfile
RUN --mount=type=secret,id=NPM_TOKEN \
NPM_TOKEN=$(cat /run/secrets/NPM_TOKEN) npm ciBuild secrets share the key space with the service's env secrets: set both with duploy secret set.
Command
command overrides the image's CMD:
toml
command = ["bundle", "exec", "sidekiq"]Health checks
| Config | Probe |
|---|---|
health = { http = "/healthz" } | HTTP GET to the service port. Any 2xx or 3xx passes. |
health = { cmd = ["pg_isready"] } | Run inside the container. Exit 0 passes. |
none, web | TCP connect to port |
none, worker | Healthy after staying up for 5 seconds |
interval (default 10s), timeout (default 5s) and retries (default 3) tune the HTTP and command probes:
toml
health = { http = "/readyz", interval = "10s", retries = 30 }A new instance gets a 60-second start window in which failed probes do not count, so slow starters are not killed while booting. After that, retries failures in a row mark it unhealthy. During a deploy, an instance that never passes fails the deploy and the old instance keeps serving.
Replicas
toml
replicas = 3Default 1. Traffic is balanced across healthy instances. A service with a volume cannot have more than one replica.
Resources
toml
resources = { cpu = 1.0, memory = "1G" }| Key | Meaning |
|---|---|
cpu | CPU cores, fractions allowed (0.25) |
memory | Hard memory limit: 512M, 2G, 256Mi, 1GB. No swap. |
pids | Process limit |
disk | Root filesystem quota |
nofile | Open file limit |
read_only | Read-only root filesystem with a writable /tmp. Volumes stay writable. |
runtime | Container runtime: runc, sysbox-runc or kata-clh |
Anything you leave out comes from your plan's defaults; values above the plan maximum are rejected when the file is parsed. The container also gets DUPLOY_CPU, DUPLOY_MEMORY_MB and GOMAXPROCS, because nproc and /proc/meminfo show the host, not your limits.
Outbound traffic
By default a service may connect anywhere on the internet. To allow only some destinations:
toml
[services.web.network]
egress = ["api.stripe.com", "*.amazonaws.com", "203.0.113.0/24"]Entries are hosts, wildcards and IP addresses or CIDRs, at most 64. The list binds only this service; other services in the project are unaffected. Services and databases inside the project are always reachable.