---
url: /guide/services.md
description: >-
  Web services, workers and cron jobs; build sources, image sources, health
  checks, replicas, resources and egress rules.
---

# 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](/guide/cron).

## 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 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

| 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 = 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" }
```

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