Docker Compose Update

Written by

in

Docker Compose Update: A Complete Guide to Updating Services Safely

Disclosure: This post contains one or more links to providers we have a real, registered affiliate/referral relationship with. We may earn a commission at no extra cost to you if you sign up through them.

Keeping containers current is one of those tasks that seems trivial until it breaks a production stack at the worst possible moment. This guide walks through how to perform a docker compose update correctly, covering image pulls, dependency ordering, rollback strategy, and the common pitfalls that turn a routine update into an incident.

Whether you’re managing a single VPS running a handful of services or a small fleet of hosts, understanding exactly what happens during a docker compose update — and what doesn’t happen automatically — is essential to running a reliable stack.

Why a Docker Compose Update Is Different From a Simple Restart

A common misconception is that restarting a service refreshes its image. It doesn’t. Docker Compose caches image references locally, and unless you explicitly pull new layers, docker compose restart will simply relaunch the exact same container from the exact same image digest. A real docker compose update requires three distinct steps: pulling new image data, recreating containers that reference changed images, and — where applicable — running any migration or initialization logic those new images expect.

This distinction matters because teams often assume their stack is “up to date” simply because it’s been restarted periodically. In reality, an image tagged latest in your docker-compose.yml can sit unchanged on disk for months unless a pull is triggered on purpose.

The Three Phases of an Update

Every meaningful docker compose update goes through the same three phases, regardless of stack size:

1. Pull — fetch new image layers from the registry.
2. Recreate — stop old containers and start new ones from the updated images, preserving volumes and networks.
3. Verify — confirm the new containers are healthy and the application behaves correctly.

Skipping verification is the single most common cause of “silent” outages — a container starts, looks running, but the application inside is crash-looping or serving errors.

The Standard Docker Compose Update Workflow

The most common and safest pattern for a docker compose update looks like this:

cd /opt/myapp
docker compose pull
docker compose up -d
docker compose ps

docker compose pull fetches the latest images for every service defined with a tag (not a locally built build: context). docker compose up -d then compares the new image IDs against the running containers and recreates only the ones that changed — services with unchanged images are left untouched, which avoids unnecessary downtime for parts of the stack that didn’t need it.

Updating a Single Service Instead of the Whole Stack

You rarely want to update everything at once, especially in a multi-service stack where a database and an application share a compose file. Targeting a single service keeps the blast radius small:

docker compose pull redis
docker compose up -d redis

This pulls and recreates only the redis service, leaving every other container — and its network connections — untouched. This pattern is especially useful when you’re testing a docker compose update against a single component before rolling it out stack-wide.

Forcing Recreation Without a New Image

Occasionally you need to force a container recreation even when the image hasn’t changed — for example, after editing environment variables or volume mounts in docker-compose.yml. The --force-recreate flag handles this:

docker compose up -d --force-recreate app

This is a distinct scenario from a true docker compose update, but it’s worth knowing the difference: pull changes the image, --force-recreate changes the container regardless of the image.

Handling Stateful Services During an Update

Updating stateless application containers is usually low-risk. Updating stateful services — databases, message queues, search indexes — carries real risk, because a new major version may expect a different on-disk data format.

  • Always check the release notes for the target image tag before updating a database container.
  • Take a fresh backup or snapshot of the associated volume before pulling a new major version.
  • Pin database images to a specific minor version (e.g., postgres:16.4 rather than postgres:16) so an unrelated docker compose pull doesn’t silently jump a version.
  • Test the update against a copy of the data on a separate host or a staging compose project first.
  • If you’re running Postgres in this kind of setup, the Postgres Docker Compose setup guide covers volume and backup conventions that make this kind of update far less risky. The same logic applies to Redis and other stateful services — know what’s persisted, and where, before you touch the image tag.

    Version Pinning: The Most Important Habit for Safe Updates

    Nothing causes more unpredictable docker compose update behavior than floating tags. If your docker-compose.yml references nginx:latest or node:20, a docker compose pull can bring in a genuinely different image than the one you tested last week, with no changelog forcing you to notice.

    services:
      web:
        image: nginx:1.27.0
        restart: unless-stopped
        ports:
          - "80:80"
        volumes:
          - ./html:/usr/share/nginx/html:ro

    Pinning to a specific tag turns every docker compose update into a deliberate, reviewable action: you change the tag in the YAML file, commit that change, and only then run the pull/recreate cycle. This also makes rollback trivial — revert the tag in version control and re-run the same two commands.

    Diffing Before You Pull

    If you’re not sure whether an update will pull anything new, docker compose pull --dry-run (available in recent Compose versions) or simply comparing digests avoids surprises:

    docker compose images
    docker compose pull
    docker compose images

    Comparing the two images outputs tells you exactly which services actually received new image IDs, which is far more reliable than assuming a pull did nothing just because the command returned quickly.

    Rebuilding Locally Built Images as Part of an Update

    If a service is built from a local Dockerfile rather than pulled from a registry, docker compose pull does nothing for it — you need to rebuild:

    docker compose build --no-cache app
    docker compose up -d app

    For projects that mix built and pulled services, a combined update command covers both cases:

    docker compose build
    docker compose pull
    docker compose up -d

    If you’re unclear on when to reach for a Dockerfile versus relying purely on Compose-managed images, the Dockerfile vs Docker Compose comparison and the more detailed Docker Compose rebuild guide are useful references for getting this workflow right before automating it.

    Zero-Downtime Considerations

    Standard docker compose up -d briefly stops the old container before starting the new one. For most internal tools and low-traffic services this brief gap is inconsequential, but for anything user-facing it’s worth understanding the limitation: Compose alone does not provide rolling updates or health-check-gated traffic cutover the way an orchestrator like Kubernetes does.

    If your stack genuinely needs zero-downtime deploys, options include:

  • Running two instances of the service behind a reverse proxy and updating them one at a time.
  • Using Docker Swarm mode (docker service update), which does support rolling updates natively.
  • Migrating to Kubernetes if the operational complexity is justified by the traffic and uptime requirements.
  • For most small-to-medium VPS deployments, a brief restart window during a scheduled docker compose update is an acceptable tradeoff against the added complexity of a full orchestrator.

    Health Checks as an Update Safety Net

    Defining a healthcheck in your compose file means Docker itself will report a container as unhealthy if the application fails to start correctly after an update, rather than just showing Up:

    services:
      app:
        image: myapp:2.3.0
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
          interval: 30s
          timeout: 5s
          retries: 3

    Checking docker compose ps after an update and confirming every service shows healthy rather than just running is a simple habit that catches a large share of failed updates immediately, instead of hours later.

    Rolling Back a Failed Update

    Because Compose doesn’t retain previous container state after up -d recreates a service, rollback means reverting the image reference and re-running the same commands:

    git checkout HEAD~1 -- docker-compose.yml
    docker compose up -d

    This is exactly why pinned, version-controlled tags matter — without them, “the previous version” isn’t a well-defined thing you can roll back to. If your compose file references secrets or environment-specific values, keeping those separated from the image tag itself (see the Docker Compose secrets guide and the Docker Compose env guide) keeps a rollback limited to just the image change, rather than dragging configuration changes along with it.

    Automating Docker Compose Updates Safely

    Fully automated updates (cron-triggered pull + up -d) are tempting but risky for anything stateful or user-facing, since there’s no human checkpoint before a bad image reaches production. A safer middle ground:

  • Automate the pull step and alert on new image availability, but leave up -d as a manual or approval-gated step.
  • Run automated updates against a staging compose project first, and only promote the same tag to production after a manual check.
  • Log every docker compose update — service name, old digest, new digest, timestamp — so a bad update is traceable after the fact. Reviewing docker compose logs (see the Docker Compose logs debugging guide) immediately after an automated update is a reasonable minimum safeguard.
  • If you’re running this kind of workflow on a lightweight VPS rather than a managed platform, providers like DigitalOcean or Hetzner are common, low-overhead choices for hosting a single-host Compose stack that you update on a schedule.


    Recommended: Ready to put this into practice? DigitalOcean is a tool we use for exactly this, and we have a real, disclosed affiliate relationship with them.

    FAQ

    Does docker compose up -d automatically pull new images?
    No. up -d only recreates containers if it detects a locally cached image ID differs from what’s currently running. If you never run docker compose pull (or use a build step), up -d will keep using whatever image is already on disk, even for a floating tag like latest.

    How do I update just one service without touching the rest of the stack?
    Pass the service name to both commands: docker compose pull <service> followed by docker compose up -d <service>. Compose will leave every other running container untouched, including shared networks and volumes.

    Is docker compose restart the same as an update?
    No. restart stops and starts the existing container from its existing image — it never pulls or rebuilds anything. A real docker compose update always requires either pull or build before up -d for the change to take effect.

    What’s the safest way to update a database container?
    Pin the image to an exact version, back up the volume first, review the target version’s release notes for breaking changes, and test the update against a copy of the data before touching production. Never rely on a floating major-version tag for a stateful service.

    Conclusion

    A reliable docker compose update comes down to a small set of disciplined habits: pin your image tags instead of floating on latest, pull deliberately rather than assuming a restart refreshes anything, treat stateful services with extra caution and a backup step, and verify health after every update rather than trusting that “running” means “working.” None of this requires exotic tooling — the built-in pull, build, and up -d commands are sufficient for the vast majority of single-host and small-fleet deployments. For teams eventually needing rolling updates with zero downtime, that’s the point to evaluate Swarm or Kubernetes rather than trying to force that behavior out of Compose alone. For the full command reference and flags used throughout this guide, the official Docker Compose CLI reference is the authoritative source to keep bookmarked.

    Comments

    Leave a Reply

    Your email address will not be published. Required fields are marked *