Skip to content

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 ​

typeWhat it isNeeds
web (default)A long-running process that opens a portport
workerA long-running process with no port and no domain—
cronA one-shot container per schedule triggerschedule
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:

FormExampleNotes
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 ci

Build 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 ​

ConfigProbe
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, webTCP connect to port
none, workerHealthy 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 = 3

Default 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" }
KeyMeaning
cpuCPU cores, fractions allowed (0.25)
memoryHard memory limit: 512M, 2G, 256Mi, 1GB. No swap.
pidsProcess limit
diskRoot filesystem quota
nofileOpen file limit
read_onlyRead-only root filesystem with a writable /tmp. Volumes stay writable.
runtimeContainer 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.

Duploy — our nodes, your file.