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:

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):

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.

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.

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.

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.

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

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