Configuration

# Roles

Different server types — web, workers — each with its own config.

Roles are used to configure different types of servers in the deployment. The most common use for this is to run web servers and job servers.

Kamal expects there to be a `web` role, unless you set a different `primary_role` in the root configuration.

## Role configuration

Roles are specified under the servers key:

```yaml
servers:
```

## Simple role configuration

This can be a list of hosts if you don't need custom configuration for the role.

You can set tags on the hosts for custom env variables (see dash docs env):

```yaml
web:
  - 172.1.0.1
  - 172.1.0.2: experiment1
  - 172.1.0.2: [ experiment1, experiment2 ]
```

## Custom role configuration

When there are other options to set, the list of hosts goes under the `hosts` key.

By default, only the primary role uses a proxy.

For other roles, you can set it to `proxy: true` to enable it and inherit the root proxy configuration or provide a map of options to override the root configuration.

For the primary role, you can set `proxy: false` to disable the proxy.

You can also set a custom `cmd` to run in the container and overwrite other settings from the root configuration.

```yaml
workers:
  hosts:
    - 172.1.0.3
    - 172.1.0.4: experiment1
  cmd: "bin/jobs"
  stop_timeout: 30
```

How long this role's container must keep running before the deploy accepts it, when there is no healthcheck to wait on. Overrides the root `readiness_delay` for this role only; roles with a healthcheck never wait on it.

```yaml
readiness_delay: 4
```

## Rolling boot

The root `boot` key paces the deploy across the whole cross-role host list, so it cannot serialize one role's hosts while another role keeps booting in parallel. Set `boot` on the role instead and its hosts are paced on their own — a second host in a worker role finally buys the redundancy it looks like it buys.

`limit` is a count, or a percentage of the hosts this run is booting for the role (so `--roles` / `--hosts` narrow the denominator too); `limit: 1` boots them one at a time. `wait` is the pause between hosts or groups, and defaults to none. It needs `limit` to do anything — without one the role's hosts boot in a single group, leaving no gap to pause in, and Kamal warns. Nothing waits after the final group.

A role using this key needs the role-first iteration of `boot/parallel_roles`. Kamal turns it on for you when you have not set it; an explicit `parallel_roles: false` is a conflict and raises instead.

```yaml
boot:
  limit: 25%
  wait: 10
```

Option values are shell-escaped before reaching `docker run`, but `${...}` is deliberately left for the deploy host's shell to expand. Role `env` is never defined in that shell, so `${MY_VAR}` resolves to empty. Use the bare `$MY_VAR` form, which is escaped and expands inside the container. Kamal rejects `${...}` in `health-*` options, where the empty value would only surface as a deploy timeout.

```yaml
options:
  memory: 2g
  cpus: 4
logging:
  ...
proxy:
  ...
labels:
  my-label: workers
env:
  ...
asset_path: /public
```

## Healthcheck

A role that does not run the proxy has no readiness gate of its own: the deploy only checks that the container is still running after `readiness_delay` seconds before stopping the old one. Declare a `healthcheck` and Kamal renders it into docker's own HEALTHCHECK flags, so the deploy waits for the container to report healthy instead.

`port` is required unless you set `cmd`. The default HTTP check is `curl -f http://localhost:<port><path> || exit 1`, so the image needs curl.

`cmd` runs inside the container via /bin/sh and can be anything that exits non-zero when unhealthy — no HTTP server and no published port needed. It may not contain `${...}`: that would expand on the deploy host, not in the container.

Durations are seconds when given as a plain number, or any docker duration string ("90s", "1m30s"). `interval` defaults to 1 second so a deploy is not left waiting on docker's own 30 second default.

A `healthcheck` cannot be combined with `health-*` keys under `options`.

`exec` is the escape hatch for an image whose HEALTHCHECK you cannot change, or for an emergency override without a rebuild. Dash `docker exec`s it on the deploy host once a second and gates the deploy on the exit code — no HTTP server and no published port needed, and unlike `cmd` it may use `${...}`, which is quoted through to the container. It is strictly worse than `cmd` in the general case, so reach for it only when `cmd` is not available:

- deploy-time only. Docker never runs it, so `docker ps` never shows `(healthy)` and `docker inspect` keeps no probe history.
- each attempt costs a process spawn on the host (the whole wait is one SSH round trip, so the cost does not grow with how long the boot takes).
- nothing outside a deploy ever runs it.

`exec` replaces docker's healthcheck rather than configuring it, so it cannot be combined with `cmd`, `port`, `path`, or any of the duration keys. The wait gives up at `deploy_timeout`; a probe that never exits zero fails the boot and leaves the old container running.

A non-proxied role with neither a `healthcheck` nor a `health-cmd` option warns on every deploy, because the readiness delay is the only thing standing between a container that starts and a container that serves. Write `healthcheck: false` to accept that gap explicitly and silence the warning.

```yaml
healthcheck:
  port: 7434
  path: /readyz
  cmd: "pgrep -f bin/jobs"
  exec: "bin/ready-check"
  interval: 5
  timeout: 3
  retries: 3
  start_period: 60
  start_interval: 1
```

> **Note:** Generated from the gem's `lib/dash/configuration/docs/role.yml` — the same reference `dash docs role` prints in your terminal, so this page always matches your installed version.