New Customers: 50% OFF Your First Month on All VPS Servers & Web Hosting Plans!

Updating a container without downtime

Deploy new container versions without interrupting service by running multiple replicas behind a reverse proxy with health checks, or using Docker Swarm or Kubernetes rolling updates. Add container health checks to ensure traffic only reaches ready instances, pin images by digest for instant rollback, and plan database migrations with the expand-and-contract approach.

Rhys CallowayLinux VPS, servers, security and the command line 9 min read Updated 23 Sep 2026 AlmaLinux 9, Ubuntu 24.04

You deploy a new version without downtime by avoiding stop-first restarts. On one host that means running at least two replicas behind a reverse proxy that respects health checks, then updating one replica at a time. Or you use an orchestrator that supports start-first rolling updates, such as Docker Swarm or Kubernetes, and add health checks plus a quick rollback route.

This picks up from a server you can already reach over SSH. Docker and Docker Compose are installed and your application already runs in containers.

Before you start

  • Compose’s default behaviour is stop-first. docker compose up recreates changed containers by stopping the old one, then starting the new one on the same port. With a single replica this causes a brief interruption. You avoid that by running multiple replicas behind a proxy that removes unhealthy instances from rotation, or by using Swarm or Kubernetes rolling updates.
  • Health checks are not optional. Define a container health check and make rollouts wait for healthy. In Compose, use a service-level healthcheck and wire dependencies with depends_on: condition: service_healthy. In Swarm and Kubernetes, use readiness checks so traffic only flows to ready instances.
  • Pin images by immutable digest. Deploy image: repo@sha256:.... Avoid :latest in production. Keeping the previous digest is what makes a rollback immediate.
  • Never remove volumes during routine updates. docker compose down -v deletes named and anonymous volumes. On databases that is irreversible data loss. Compose preserves volumes when recreating containers. Do not use --renew-anon-volumes on stateful services you intend to keep.
  • Avoid port conflicts when you scale. Two containers cannot bind the same host port. Put your app behind a reverse proxy, for example Traefik or NGINX, so only the proxy publishes the host port. Backends listen on container ports and the proxy load balances.
  • Plan your database migrations. Use the expand-and-contract approach so the old and new versions can run during the rollout. For PostgreSQL, use CREATE INDEX CONCURRENTLY and staged constraint validation. For MySQL, prefer online DDL with ALGORITHM=INPLACE or INSTANT and LOCK=NONE, and use pt-online-schema-change or gh-ost on very large tables.

Step 1: Pull the new image and pin versions

First fetch the new image to your host, and make sure your compose file pins images by digest. Pulling ahead of time avoids timing your update on a slow registry fetch.

What this command does: pulls the latest allowed version of your service’s image according to policy, without changing running containers.

AlmaLinux 9 and Ubuntu 24.04:

docker compose pull <SERVICE>

Alternatively, you can make up pull images for you during the update. What this command does: recreates the specified service and tells Compose to pull the image before recreating containers.

AlmaLinux 9 and Ubuntu 24.04:

docker compose up -d --no-deps --pull always <SERVICE>

To harden your compose file, set a pull policy and pin by digest. The pull policy makes image refresh behaviour explicit.

services:
  web:
    image: myrepo/example@sha256:abc123...   # pinned, not a mutable tag
    pull_policy: always                      # or daily, weekly, every_12h

Step 2: Add real health checks

Traffic should only reach the new version when it is able to serve. Add a container health check and wire dependencies to wait for health. Your reverse proxy can then remove unhealthy instances from rotation.

Container-level health check in Dockerfile

What this does: tells Docker how to test whether the container is healthy from inside the container. Your script should return exit code 0 when ready to serve real traffic.

# Dockerfile
HEALTHCHECK --interval=10s --timeout=3s --retries=10 --start-period=15s \
  CMD /usr/local/bin/healthcheck.sh

Service-level health check in Compose

What this does: defines a health check for the running container and makes dependent services wait for healthy before starting.

services:
  web:
    image: myrepo/example@sha256:abc123...
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://localhost:8080/health || exit 1"]
      interval: 10s
      timeout: 3s
      retries: 10
      start_period: 15s

  worker:
    image: myrepo/worker@sha256:def456...
    depends_on:
      web:
        condition: service_healthy

On the edge, configure your proxy to health check backends. Traefik, for example, removes unhealthy servers from its balancing pool.

Step 3: Choose your rollout method

You have three sound options. Pick one.

  • Compose on one host behind a reverse proxy, with at least two replicas. This is the smallest change for many single-server deployments.
  • Docker Swarm with start-first rolling updates. This provides an orchestrated rolling update on one or more nodes.
  • Kubernetes rolling updates. This is the standard behaviour for Deployments with a correct readiness probe.

Step 4: Update with Docker Compose behind a reverse proxy

This path keeps traffic flowing by running two or more replicas and letting the proxy route around a replica while it is restarting. Ensure only the proxy publishes host ports. The app service should not use ports: that bind to the host, otherwise you will hit a port conflict when scaling.

4.1 Scale to at least two replicas

What this command does: starts or adjusts the number of containers for the service to the requested count.

AlmaLinux 9 and Ubuntu 24.04:

docker compose up -d --scale web=2

Confirm your proxy is health checking backends and only routing to healthy ones.

4.2 Pull and recreate one service while keeping the pool healthy

What this command does: pulls the image and recreates the service’s containers. Compose replaces containers stop-first, but with two or more replicas and a proxy that removes unhealthy instances, traffic keeps flowing to the remaining healthy replica while each new replica starts.

AlmaLinux 9 and Ubuntu 24.04:

docker compose up -d --no-deps --pull always web

What this command does: waits for services to reach running or healthy state before returning. Use this in scripts to block until your new replicas pass health checks.

AlmaLinux 9 and Ubuntu 24.04:

docker compose up -d --wait web

4.3 Scale down if you want to return to one replica

What this command does: reduces the number of containers for the service.

AlmaLinux 9 and Ubuntu 24.04:

docker compose up -d --scale web=1

Keep your previous image digest noted. If you need to roll back, change the image: in your compose file back to the prior digest, then recreate the service.

What this command does: recreates the service using the image pinned in your compose file.

AlmaLinux 9 and Ubuntu 24.04:

docker compose up -d --no-deps web

Important: do not run docker compose down -v as part of routine updates. It will delete volumes, including database data.

Step 5: Update with Docker Swarm rolling updates

Swarm offers a true start-first handover. You can set behaviour either in the compose file under deploy: when deploying a stack, or at the CLI when updating a service.

5.1 Configure start-first and rollback in your compose file for stacks

What this does: tells Swarm to start the new task before stopping the old one, update one at a time, and roll back automatically if health fails within the monitor window.

services:
  web:
    image: myrepo/example@sha256:abc123...
    deploy:
      update_config:
        order: start-first
        parallelism: 1
        monitor: 30s
        failure_action: rollback
      rollback_config:
        order: start-first
        parallelism: 1

Note: the deploy: section is for platforms that implement it. Docker Compose on its own host may ignore it. Swarm honours it.

5.2 Run a rolling update with automatic rollback

What this command does: updates the running Swarm service to a new image and applies rolling update behaviour. It starts a new task first, monitors health, and rolls back if the update fails.

AlmaLinux 9 and Ubuntu 24.04:

docker service update \
  --image myrepo/example@sha256:abc123... \
  --update-parallelism 1 \
  --update-order start-first \
  --update-monitor 30s \
  --update-failure-action rollback \
  web

If you need to revert to the prior spec and image, use rollback.

What this command does: restores the service to the last known good state.

AlmaLinux 9 and Ubuntu 24.04:

docker service rollback web

Step 6: Update with Kubernetes rolling updates

Kubernetes Deployments roll by default. You must set a correct readiness probe so Pods are only added to Service endpoints when actually ready. For zero unavailable replicas during rollout, set maxUnavailable: 0 and a non-zero maxSurge.

6.1 Ensure readiness and strategy

apiVersion: apps/v1
kind: Deployment
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  template:
    spec:
      containers:
      - name: web
        image: myrepo/example@sha256:abc123...
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          periodSeconds: 10
          timeoutSeconds: 3
          failureThreshold: 10

6.2 Roll the Deployment and watch it

What this command does: updates the container image for a named container in your Deployment. Pin by digest for predictable rollbacks.

AlmaLinux 9 and Ubuntu 24.04:

kubectl set image deployment/web web=myrepo/example@sha256:abc123...

What this command does: watches rollout progress until the Deployment is available or a timeout occurs.

AlmaLinux 9 and Ubuntu 24.04:

kubectl rollout status deployment/web

If you need to undo the last change, use rollout undo.

AlmaLinux 9 and Ubuntu 24.04:

kubectl rollout undo deployment/web

Step 7: Handle database migrations without downtime

Order matters. Use the expand-and-contract pattern so old code and new code can both run during the rollout window.

  1. Expand: deploy additive, backward-compatible schema changes. Add new tables or columns with defaults that do not break current reads or writes. If needed, backfill in the background.
  2. Flip: deploy the new application version that reads the new schema while still tolerating the old one.
  3. Contract: once all traffic is on the new version, remove old columns and code paths.

PostgreSQL tips

  • Create indexes without blocking writes. Use CREATE INDEX CONCURRENTLY. It cannot run inside a transaction block.
-- Runs without blocking writes, but not inside a transaction
CREATE INDEX CONCURRENTLY idx_orders_created_at ON orders (created_at);
  • Stage constraints to avoid long locks. Add a constraint as NOT VALID, then validate it later, which avoids an immediate table-wide lock.
ALTER TABLE users
  ADD CONSTRAINT users_email_chk CHECK (email <> '') NOT VALID;

ALTER TABLE users
  VALIDATE CONSTRAINT users_email_chk;

MySQL tips

  • Prefer online DDL where supported. Use ALGORITHM=INPLACE or INSTANT with LOCK=NONE. Expect brief metadata locks at the start or end.
ALTER TABLE users
  ADD COLUMN nickname VARCHAR(64) NULL,
  ALGORITHM=INPLACE, LOCK=NONE;
  • For very large changes, use online migration tools. pt-online-schema-change and gh-ost keep writes online by migrating data into a shadow table and swapping it in.

Step 8: Verify, then keep the rollback within reach

Wait for health before declaring the update complete.

  • Compose: use docker compose up --wait so your automation blocks until services are healthy. Remember this does not change stop-first behaviour, it only waits for health.
  • Swarm: set --update-monitor and --update-failure-action rollback so a bad rollout reverts itself.
  • Kubernetes: watch kubectl rollout status and have kubectl rollout undo ready.

Keep the prior image digests in your deployment notes. That is your fastest path to a clean rollback.

What next

If you want a steady home for this setup, our London VPS range is designed for running Docker-based workloads. You can pick the plan and size that fits and deploy in the UK or US.

  • Browse our Linux VPS plans.
  • Keep exploring our VPS guides for networking, security and operations topics you will meet as you grow.

If you need help with a Hostworld server, please open a support ticket and tell us which method you are using, the compose or manifest snippet, and the exact command you ran.

Next step: automate this rollout in your CI so new digests are deployed through the same health-checked path every time.

Common questions

Can I get zero downtime with Docker Compose alone on one replica?

No. Compose replaces a changed container stop-first on a single replica. You need either multiple replicas behind a reverse proxy that removes unhealthy instances, or an orchestrator with start-first rolling updates.

Do I need a load balancer to run two replicas?

Yes, in practice. Two containers cannot both bind the same host port. Put a reverse proxy in front so only the proxy binds the host port and it load balances to backends. Configure proxy health checks so starting or unhealthy containers are not in rotation.

How do I roll back quickly with Compose?

Pin images by digest. Keep the previous digest recorded. Change your compose file’s image: back to the prior @sha256:... and run docker compose up -d --no-deps <SERVICE>. Do not tear down the stack or remove volumes.

Why did my update hang even though I added health checks?

Overly strict health checks or a probe that does not reflect “ready to serve” can block good instances, and missing checks can route traffic too early. Make sure your health endpoint includes all warm-up dependencies and that retry thresholds allow start-up time.

Is it safe to run migrations during the rollout?

Yes if you order them. Use expand-and-contract. In PostgreSQL use concurrent index creation and staged constraint validation. In MySQL use online DDL with INPLACE or INSTANT and LOCK=NONE. For very large tables use tools such as pt-online-schema-change or gh-ost.