Docker Deployment: Deploy Using Docker With Zero Downtime in Production

Last updated August 13, 2026
# the short answer

A plain docker compose up recreates a service by stopping the old container before starting the new one, which leaves a gap of roughly 10 to 20 seconds where requests fail. Three approaches close it: Docker Swarm rolling updates with --update-order start-first, the docker rollout CLI plugin for Compose projects, or a reverse proxy that you point at a second container once it is healthy. All three depend on a real HEALTHCHECK, because every one of them decides when to cut traffic over based on whether the container reports healthy.

Skip the manual setup. DeployManage provisions your server and ships zero-downtime deploys on any cloud.

$ get started free

Most Docker deployment guides stop at docker compose up -d. That command works, it is what everyone runs, and on a production site it drops requests every single time. The gap is short enough that you will not notice it in testing and long enough that a monitoring check or a customer mid checkout absolutely will.

This is not a Docker bug. Compose is doing exactly what it says: recreating a container. Recreating means stopping the running one and starting a replacement, in that order. Between those two events nothing is listening on the port. Reported gaps sit around 10 to 20 seconds for a typical web application once you count container start plus framework boot.

Why does docker compose up cause downtime?

Because Compose's default recreate strategy is stop then start, and because there is only one container. A container is not a process you can gracefully hand off to a replacement the way a process manager reloads workers. Compose tears down the old container and creates a new one from the updated image, so for the duration of image start plus application boot, the port is dead.

Compose on its own has no rolling update mechanism. That capability lives in Swarm mode. So you have three honest options, and picking between them is mostly a question of how much machinery you want to run.

ApproachWhat it needsGood forMain drawback
Docker Swarm rolling updateSwarm mode enabled, service defined as a stack Multiple replicas, multi node, built in rollback You have to adopt Swarm and rewrite compose files as stack files
docker rollout pluginA CLI plugin, plain Compose otherwise Single server Compose setups you do not want to rewrite Third party tool, briefly runs double the containers
Reverse proxy swapnginx, Traefik or Caddy in front, two container sets Full control, works with anything, true blue green You build and maintain the switching logic yourself

Option 1: Docker Swarm rolling updates

Swarm updates a service by replacing its tasks one at a time. Docker's own rolling update documentation describes the sequence precisely: stop the first task, schedule the update for that stopped task, start the container for the updated task, and if the task returns RUNNING, wait for the configured delay before starting the next one. If a task returns FAILED at any point, the update pauses rather than marching on through your whole fleet.

Two defaults are worth knowing. The scheduler updates one task at a time unless you raise --update-parallelism. And --update-order defaults to stop-first, which is the setting that reintroduces the exact problem you came here to solve. On a single replica service, stop-first means the old container is gone before the new one exists.

docker service update \
  --image myapp:2026.08.13 \
  --update-order start-first \
  --update-parallelism 1 \
  --update-delay 10s \
  --update-failure-action rollback \
  myapp_web

start-first brings the new task up and only stops the old one once the replacement is running, so capacity never dips below your replica count. The trade off is that you briefly run more containers than you asked for, which means the server needs headroom for an extra copy of the application. On a 2 GB box running a heavy framework, that headroom is not free.

Setting --update-failure-action rollback is the part people skip. Without it a failed update pauses and leaves you half deployed at 2am. With it, Swarm reverts to the previous image itself.

Option 2: docker rollout, for Compose setups you do not want to rewrite

If you run a single server with a Compose file and adopting Swarm feels like a lot of ceremony for one host, the docker rollout CLI plugin does the same trick without it. It scales the service to double the current number of instances, waits for the new containers to pass their health check, then removes the old ones. That is the same shape as a Kubernetes rolling update, implemented as a seventy line shell plugin.

docker rollout -f docker-compose.yml web

It is a third party tool rather than something Docker ships, so treat it accordingly: pin the version you tested, and understand that it leans entirely on your health check being honest. If your health check returns 200 the instant the web server binds, before migrations or cache warming have finished, rollout will happily cut traffic to a container that cannot serve a real request.

Option 3: the reverse proxy swap

The oldest and most controllable approach: run the proxy outside the deployment, start the new container set alongside the old, verify it, then change what the proxy points at and drain the old one. This is straightforward blue green deployment applied to containers, and it is the only one of the three that lets you keep the old version running and warm for an hour after the switch in case you need it back.

With nginx in front, proxy_next_upstream error timeout http_502 http_503 means an in flight request that hits a container going away gets retried against the healthy upstream instead of failing. That single line covers a lot of the ragged edge during a swap.

Health checks are the load bearing part

Every approach above answers the same question: is the new container ready to take traffic? They all answer it by reading the container's health status. So a vague health check quietly turns a zero downtime deploy back into a normal one, except now you also believe it is safe.

The documented HEALTHCHECK defaults are an interval of 30s, a timeout of 30s, a start period of 0s and 3 retries, with exit status 0 meaning healthy and 1 meaning unhealthy. It takes that many consecutive failures for the container to be marked unhealthy. Those defaults are built for detecting a sick long running container, not for a deploy: a 30 second interval means a deployment tool may wait half a minute to learn something it could have known in two seconds.

HEALTHCHECK --interval=5s --timeout=3s --start-period=30s --retries=3 \
  CMD curl -fsS http://localhost:8000/health || exit 1

Tighten the interval so the switch happens quickly. Set a start period generous enough to cover your application's real boot time, because checks that fail during the start period do not count against the retry budget. And point the check at an endpoint that touches what actually matters: a route that opens a database connection and confirms the cache is reachable tells you something. A static route that returns "ok" tells you nginx is running, which was never in doubt.

What about database migrations?

Containers do not change the hard part. During a rolling update, old and new containers run at the same time against one database, so the schema has to satisfy both simultaneously. Adding a NOT NULL column without a default, renaming one, or dropping one still breaks whichever version was not expecting it, no matter how elegantly the containers swapped over.

The pattern that works is expand and contract: deploy a schema change that both versions tolerate, deploy the code, then clean up in a later release. Run migrations as a separate one shot step before the rolling update starts, never in the container entrypoint, or every replica will race to run the same migration at once. The mechanics are the same ones covered in zero downtime database migrations.

How do you verify a Docker deployment actually had zero downtime?

Not by watching the deploy log. It will say the new container is healthy whether or not requests were served. The only honest test is measuring from outside the host while the deploy runs.

The cheap version is a loop hitting the endpoint every second through a deploy and counting non 200 responses. The version that catches the deploys you are not watching is an external uptime check running continuously against the production URL, so a Tuesday afternoon release that dropped eleven requests shows up as an incident instead of disappearing into the log. Deploy gaps are short by definition, so a check that runs every five minutes will miss almost all of them. Frequency is the whole game here.

Rollback matters more than the update

Every strategy here is only half a plan without a way back. Swarm keeps the previous service specification and docker service rollback myapp_web returns to it. With the proxy swap, the old container set is still sitting there and rollback is a proxy change. With docker rollout, your rollback is redeploying the previous image tag, which is why deploying the latest tag is a mistake: you cannot roll back to a tag that has already moved.

Tag every image with something immutable, a commit SHA or a date stamp, and keep the last few around on the host. Rollback then costs seconds instead of a rebuild. The same logic applies to any release system, containerised or not, and it is covered in more depth in our guide to deployment rollback strategies.

Do you need containers for zero downtime deployment at all?

No. Symlink based atomic releases achieve the same result without a container runtime: build the new release in a fresh directory, run migrations, swap a current symlink and reload the process manager. The swap is a single atomic filesystem operation, so there is no window where the application is missing. That is how most PHP, Ruby and Node deployments work, and it is what DeployManage does when it runs a zero downtime deployment from your Git repository.

Containers earn their place when you need identical environments across machines, painless horizontal scaling, or dependencies that are miserable to install on a host. If you have one application on one server and you adopted Docker mostly to get repeatable deploys, an atomic release workflow gets you there with less to operate. Pick the one that matches the problem you actually have, not the one with better conference talks.

Ready to stop managing servers by hand? DeployManage provisions, deploys and monitors your fleet from one dashboard.

$ get started free

Frequently asked questions

Why does docker compose up cause downtime?

Because Compose recreates a service by stopping the old container before starting the new one. Between those two events nothing is listening on the port, which typically produces a gap of 10 to 20 seconds once container start and application boot are counted. Compose has no rolling update mechanism of its own; that capability lives in Docker Swarm mode.

How do you deploy Docker containers with zero downtime?

Use one of three approaches: Docker Swarm rolling updates with --update-order start-first, the docker rollout CLI plugin for plain Compose projects, or a reverse proxy that you point at a new container set once it is healthy. All three depend on a real HEALTHCHECK, because each one decides when to cut traffic over based on container health.

What is the default update order in Docker Swarm?

The default is stop-first, which stops the old task before starting its replacement. On a single replica service that guarantees downtime. Setting --update-order start-first brings the new task up first and stops the old one only once the replacement is running, at the cost of briefly running more containers than the replica count.

How many tasks does Docker Swarm update at once?

By default the scheduler updates one task at a time. After each task returns RUNNING it waits for the configured update delay before starting the next. If a task returns FAILED the update pauses instead of continuing. Raise --update-parallelism only if you have enough replicas that taking several out at once still leaves capacity.

What are the default Docker HEALTHCHECK values?

The documented defaults are an interval of 30 seconds, a timeout of 30 seconds, a start period of 0 seconds and 3 retries. Exit status 0 means healthy and 1 means unhealthy, and it takes three consecutive failures to mark a container unhealthy. For deployments these are too slow; a 5 second interval switches traffic far sooner.

What is docker rollout?

A third party Docker CLI plugin that adds zero downtime deployment to plain Compose projects. It scales the service to double the current instance count, waits for the new containers to pass their health check, then removes the old ones. It is the same pattern as a Kubernetes rolling update without requiring Swarm or Kubernetes.

Should I run database migrations inside the container entrypoint?

No. During a rolling update several replicas start at once and each would race to run the same migration. Run migrations as a separate one shot step before the update begins. Old and new containers also serve traffic simultaneously, so the schema must satisfy both versions at once, which means expand and contract changes.

How do I roll back a Docker deployment?

In Swarm, docker service rollback returns to the previous service specification. With a proxy swap, the old container set is still running and rollback is a proxy change. With docker rollout you redeploy the previous image tag. This only works if you tag images immutably by commit SHA or date; you cannot roll back to a latest tag that has moved.

Do containers give you zero downtime deployment automatically?

No. Containers make environments reproducible, but the default deploy command still stops the old container before starting the new one. Zero downtime comes from the update strategy and the health check, not from containerization itself. A non containerized symlink release swap achieves the same result with less machinery.

How do I verify a deployment really had no downtime?

Measure from outside the host while the deploy runs, because the deploy log will report success either way. Poll the production URL every second through a release and count non 200 responses, and keep a continuous external uptime check running at high frequency so gaps during releases you are not watching still get recorded.

# related

Provision and deploy from one dashboard.

Connect a provider and ship your first zero-downtime deploy in minutes.

$ get started free

# free plan · no credit card required