Deploying

# Canary rollout

Run a new version next to the live one and send it a slice of traffic before you commit.

## Lifecycle

A normal `dash deploy` replaces the live version. A rollout starts a second version alongside it, registered with dash-proxy as the service's **rollout target**, and lets you decide how much traffic it sees. Three subcommands, in order:

```shell
dash app rollout deploy                      # boot the newest image as the rollout target
dash app rollout deploy --version 3f9c2a1    # or a specific version
dash app rollout set --percent 10            # 10% of cookie-carrying traffic
dash app rollout set --list beta,qa-team     # plus everyone whose cookie is one of these
dash app rollout stop                        # all traffic back to the live version
```

`rollout deploy` builds nothing — it takes the most recent image already in the registry (or `--version`), takes the deploy lock, and on every proxy host boots that version's container for each proxied role, then registers it with `kamal-proxy rollout deploy`. dash-proxy waits up to `deploy_timeout` for the new target to become healthy, exactly as it does for a normal deploy, and the boot fails (and the container is stopped) if it does not. Booting a version that is already deployed on the host is refused.

Until you run `rollout set`, the target is registered but receives **no** traffic. `set` takes `--percent`, `--list`, or both; running it before `deploy` fails with "rollout target not set".

## How dash-proxy picks a target

Routing is keyed on one cookie, `kamal-rollout`. For each request:

1. No `kamal-rollout` cookie → the live version. Always.
2. The cookie's value is in `--list` → the rollout target.
3. Otherwise the value is hashed (FNV-1a) and lands in the rollout target if the hash falls below `--percent`% of the hash space.

Two consequences worth designing around. First, **your app sets the cookie** — dash-proxy never does. Set it to something stable per user or per session and the same person sees the same version on every request; leave it unset and nobody is ever routed to the canary. Second, the split is deterministic on the cookie value, not random per request, so `--percent 10` means "the 10% of values whose hash is lowest", which is the same 10% of people across the whole rollout.

The rollout target is a full target of the service: it is health checked, drained on stop, and shares the live service's host, TLS, buffering, and logging options — `rollout deploy` accepts only the target and the timeouts.

## With a load balancer

`rollout` runs on the proxy hosts, for roles that run the proxy. With the [load balancer](https://dash.zoolutions.llc/docs/load-balancing) in front of the fleet, the rollout target is registered on each per-host proxy next to its live container, and the load balancer keeps distributing across hosts as before. The cookie decision is made on the per-host proxy, so the canary share is per host: `--percent 10` on three hosts is 10% on each, not 10% of one.

## Ending a rollout

`rollout stop` clears the traffic split: every request goes to the live version again. The canary container keeps running and stays registered as the rollout target, so `rollout set` can resume the experiment without a new boot.

To promote the canary, deploy it: `dash deploy` (or `dash app boot --version <canary>`) runs the normal boot for that version — the existing container of that version is renamed out of the way, the new one becomes the live target, and the previous live version is stopped. Run `rollout stop` first so the split is not left pointing at a target that is about to be replaced, and `dash prune containers` to clear the stopped ones afterwards.

> **Warning:** A rollout is not ended by deploying something else. The split survives an unrelated `dash deploy` of a third version — stop it explicitly.