# docker-rollout: zero downtime deploys for Docker Compose

> docker-rollout is a Docker CLI plugin that replaces `docker compose up -d <service>` with a scale-up, wait, remove-old sequence. It suits single-server Compose setups that already run Traefik or nginx-proxy, and it cannot work with a fixed `container_name` or a published port.

**wowu/docker-rollout** — 🚀 Zero Downtime Deployment for Docker Compose

- Repository: https://github.com/wowu/docker-rollout
- Website: https://docker-rollout.wowu.dev/
- Stars: 3,336 · Forks: 104
- Language: Shell
- License: MIT
- Published: 2026-09-24 · Updated: 2026-09-24 · Language: en
- Canonical page: https://hysenlabs.com/en/projects/wowu-docker-rollout

## The gap docker-rollout fills between compose up and Kubernetes

`docker compose up -d <service>` stops the old container before starting the new one. If the application takes a while to boot, that gap is visible to users. The project's own README frames the problem this way: "Using `docker compose up` to deploy a new version of your app causes downtime because the app container has to be stopped before the new container is created."

The README also states the intended audience by exclusion. Kubernetes and Nomad are described as potentially overkill for a single-server Compose setup. Dokku ships zero downtime deployment and more features, but the README argues it is less flexible than Compose. docker-rollout sits in that middle space: you keep your compose file, your proxy and your deployment script, and you change one command.

The audience is therefore narrow and specific. You need a reverse proxy in front of the app, because multiple containers of the same service will exist at once during a rollout. The README lists Traefik and nginx-proxy as the two examples. Without one, scaling to two instances gives you two containers and no routing rule that prefers the healthy one.

## How the scale-up, wait, remove-old sequence actually runs

The mechanism is stated plainly in the README: the command "will scale the service to twice the current number of instances, wait for the new containers to be ready, and then remove the old containers." There is no separate scheduler and no agent. The plugin is a shell script, so the whole deployment is a sequence of Docker CLI and Compose calls executed locally against the same daemon the rest of your script uses.

The waiting step has two modes. If the service defines a healthcheck, in the Dockerfile or in the compose file, docker-rollout polls until the new container reports healthy, bounded by `--timeout` which defaults to 60 seconds. If there is no healthcheck, it falls back to a fixed sleep controlled by `--wait`, default 10 seconds. That difference matters: an app with a slow boot and no healthcheck will be reported as deployed after ten seconds regardless of whether it can serve traffic.

A consequence of the scale-up approach is visible in the container names. The README warns that "each deployment will increment the number in container name (e.g. `project-web-1` -> `project-web-2`)." Nothing reuses a name, so anything that resolves the service by a fixed container name will break. Proxies that watch Docker labels or the Compose network are unaffected; scripts that hardcode `docker exec project-web-1` are not.

## Installing the plugin and running a first rollout

Installation is three shell commands that place the script where the Docker CLI looks for plugins. The README gives this sequence for a per-user install:

```bash
mkdir -p ~/.docker/cli-plugins
curl https://raw.githubusercontent.com/wowu/docker-rollout/main/docker-rollout -o ~/.docker/cli-plugins/docker-rollout
chmod +x ~/.docker/cli-plugins/docker-rollout
```

After that, `docker rollout --help` should list the plugin. If you run Docker through sudo or want the plugin available to every user on the host, the README says to install it to `/usr/local/lib/docker/cli-plugins/` instead of the home directory path.

A first real use replaces the update step in an existing deployment script. The README's sample script for a `web` service pulls code, builds the image, runs migrations, then rolls out:

```bash
git pull
docker compose build web
docker compose run --rm web rake db:migrate
docker rollout web
```

The command accepts the same file selection flags as Compose. `docker rollout -f docker-compose.yml <service-name>` is the form shown in the README, and `-f` can be repeated. Other options mirror Compose: `--env-file`, `-p | --project-name`, `--profile`. The rollout-specific ones are `-t | --timeout` (default 60), `-w | --wait` (default 10), `--wait-after-healthy` (default 0) and `--pre-stop-hook`.

If both the `docker compose` plugin and the standalone `docker-compose` command are present, the README states docker-rollout prefers `docker compose`.

## The container_name and ports constraint that decides your compose file

The README's caveat list contains the constraint most likely to stop an adoption. A service cannot have `container_name` and `ports` defined in `docker-compose.yml`, because it is not possible to run multiple containers with the same name or the same host port mapping. Since the rollout runs two instances of the service at once, either of those keys makes the second container fail to start.

This is not a bug that a config flag can work around. It is a direct consequence of the scale-up design. A compose file that publishes `ports: - "3000:3000"` on the app service has to move that mapping to the proxy, and the app has to be reachable over the Compose network instead. A compose file that sets `container_name: myapp` has to drop it and let Compose generate the numbered name.

For a project that already runs Traefik or nginx-proxy with label-based discovery, this is often already true and the migration is small. For a project that exposes the app port directly to the host and has no proxy, docker-rollout is the wrong tool until that changes. The README is explicit that a proxy is required to route traffic, not optional.

## Connection draining and the pre-stop hook

Removing the old container immediately can drop requests that are still being processed. The README calls this out as a caveat and offers a workaround built on healthchecks. The idea is to make the old container report unhealthy on purpose, so the proxy stops routing to it before it is stopped.

The healthcheck must fail when a sentinel file exists. The README shows the version for a service that has no healthcheck yet:

```yml
services:
  web:
    image: myapp:latest
    healthcheck:
      test: test ! -f /tmp/drain
      interval: 5s
      retries: 1
```

If a healthcheck already exists, the drain test is prepended with `&&` to the existing command. Then the rollout is run with a hook that creates the file and waits:

```bash
docker rollout web --pre-stop-hook "touch /tmp/drain && sleep 10"
```

The same hook can live in the compose file as the label `docker-rollout.pre-stop-hook`. There is a timing trap here that the README flags in bold: the sleep must exceed the healthcheck `interval` multiplied by `retries`, plus the time needed to finish open requests. The README's own arithmetic example is interval 10s, retries 3, plus 5s, giving a sleep of 35. A sleep that is too short means the proxy never marks the container unhealthy before it is removed, and draining does not happen.

One more detail worth reading twice: docker-rollout reads labels from the old container, so a label added in this deployment applies on the next one. CLI options take priority over labels, which is the escape hatch if you need the hook immediately.

## Where docker-rollout stops and something else should start

The README names two alternatives and is honest about the trade. Kubernetes and Nomad are described as possibly overkill for a single-server Compose setup; Dokku comes with zero downtime deployment and more features but is less flexible than Compose. The difference in approach is architectural. Kubernetes and Nomad schedule containers across a cluster and manage their own service discovery and rollout state. Dokku owns the whole deployment pipeline with a git push workflow and its own proxy configuration. docker-rollout does neither: it is a shell script that drives the Docker CLI you already use, and it assumes you have already solved routing.

That makes the failure modes predictable. If your single server goes down, the rollout does not help; there is no second host. If your proxy is not configured to pick up new containers, the rollout completes and traffic still goes to the old one until it is removed. If your app has no healthcheck and boots slowly, the fixed `--wait` window is a guess. And if you need rollbacks, coordination across services, or scheduled placement, the README does not document any of that, so plan for it outside the tool.

The project is not archived, and the last push was on 2026-07-12, which is the same date as the v0.14 release. The release cadence visible in the repository is roughly one minor release per several months. It is a small, single-purpose script under the MIT license, so the upgrade surface is the script itself plus the CLI flags it passes to Compose.

## Conclusion

Adopt docker-rollout if you run a single-server Docker Compose stack behind Traefik or nginx-proxy and your service definitions avoid container_name and ports. Do not adopt it if your compose file pins a container_name, publishes a host port directly, or if you need orchestration features such as scheduling across hosts. Before the first production rollout, verify three things: that the service has no container_name and no ports entry, that your proxy discovers containers by label or by the Compose network rather than by a fixed name, and that your healthcheck timeout budget is longer than the app's boot time, since the default is 60 seconds. If you want connection draining, confirm the drain healthcheck and the pre-stop hook sleep are in place, because the README notes the label form of the hook only takes effect on the following deployment.

## FAQ

### How do I install docker-rollout?

Create ~/.docker/cli-plugins, download the docker-rollout script from the repository into that directory with curl, and make it executable with chmod +x. For a system-wide install or when Docker runs under sudo, the README says to use /usr/local/lib/docker/cli-plugins/ instead.

### Does docker-rollout work without a reverse proxy?

No. The README lists a proxy such as Traefik or nginx-proxy as required to route traffic, because the rollout runs two instances of the service at the same time and something has to decide which one receives requests.

### Why does docker-rollout fail when my service sets container_name or ports?

The README states a service cannot have container_name and ports defined in docker-compose.yml, since it is not possible to run multiple containers with the same name or the same port mapping. The rollout scales the service to twice the current instance count, so either key blocks the second container.

### What is the default timeout for waiting on a healthy container?

The -t | --timeout option defaults to 60 seconds and applies when the container has a healthcheck defined in the Dockerfile or docker-compose.yml. Without a healthcheck, docker-rollout instead waits for the -w | --wait period, which defaults to 10 seconds.

## Sources

- [License: MIT](https://github.com/wowu/docker-rollout/blob/main/LICENSE)
- [Project website](https://docker-rollout.wowu.dev/)
- [README](https://github.com/wowu/docker-rollout/blob/main/README.md)
- [Releases](https://github.com/wowu/docker-rollout/releases)
- [wowu/docker-rollout on GitHub](https://github.com/wowu/docker-rollout)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/en/projects/wowu-docker-rollout
