Docker & ContainersXXIX Β· Docker UpgradesUpgrade anatomy
What actually moves in a Docker upgrade
What you'll learn
- Name the five packages a Docker installation is made of and what each contains
- Read `docker version` and identify which component owns a given behaviour
- Explain client/server API negotiation and when version skew matters
- Predict which containers are disturbed by upgrading which component
Prerequisites
Verified against Docker Engine 29.x Β· Docker Engine 28.x Β· Docker Compose 2.x Β· containerd 2.x Β· runc 1.2.x Β· BuildKit 0.20+ Β· Linux kernel 5.15+ Β· Ubuntu 24.04 LTS Β· Debian 12 (Bookworm) Β· 2026-08-11
βUpgrading Dockerβ sounds like one operation on one thing. It is up to five packages, holding four independently versioned binaries, and the operational consequences differ sharply depending on which of them actually changed.
This lesson is the map. The next five lessons are the procedure.
The five packages
On a Debian or Ubuntu host installed from Dockerβs repository:
| Package | Contains | Restarting it affects |
|---|---|---|
docker-ce | dockerd, the daemon | The control plane; containers only if live restore is off |
docker-ce-cli | the docker command | Nothing running β it is a client binary |
containerd.io | containerd and runc | The supervisor; running shims survive |
docker-buildx-plugin | docker buildx | Builds only |
docker-compose-plugin | docker compose | Nothing running β it is a client |
Two of those five β the CLI and the Compose plugin β cannot disturb a running container at all. They are client-side binaries that talk to the daemon over a socket. Upgrading them during business hours is a non-event.
The containerd.io package is the one people misread. It carries both
containerd and runc, which is why a runc CVE fix arrives as a containerd.io
version bump with no change to the Docker version at all.
Reading the versions
$ docker versionClient: Docker Engine - Community
Version: 29.7.1
API version: 1.55
Go version: go1.26.5
Git commit: e9452d6
OS/Arch: linux/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 29.7.1
API version: 1.55 (minimum version 1.40)
Go version: go1.26.5
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: v2.2.6
GitCommit: 11ce9d5f3c68c941867e82890e93e815c1304f1b
runc:
Version: 1.3.6
GitCommit: v1.3.6-0-g491b69ba
docker-init:
Version: 0.19.0
GitCommit: de40ad0Illustrative output
Four version numbers under Server, and they move on independent schedules:
- Engine β Dockerβs own version, calendar-based (
YY.MM.patch). - containerd β upstream containerdβs semantic version.
- runc β upstream runcβs semantic version. This is the one that matters for container-escape CVEs.
- docker-init β the
tini-derived init used by--init. Rarely changes.
docker info gives you the same versions plus the runtime configuration; use
whichever you prefer, but get into the habit of recording all four before an
upgrade rather than just the Engine version.
The version scheme
Docker Engine uses calendar versioning: 28.3.2 is the third feature
release of 2028βs cycle line, patch 2. The important consequence for
operations is that the middle number is a feature release, not a minor
version in the semver sense. A move from 28.2.x to 28.3.x can add flags,
change defaults, and deprecate things.
apt-cache madison docker-ce
apt-cache madison containerd.io
apt-cache policy docker-cemadison lists every version in the configured repositories, newest first.
policy shows what is installed against what is the candidate. Between them
you know exactly what an unpinned apt-get upgrade would do.
Client and server skew
The CLI and the daemon negotiate an API version on every connection. The client offers its version; the daemon replies with the highest it supports; the client uses the lower of the two.
This is why upgrading docker-ce-cli alone is safe, and also why a host that
has been upgraded piecemeal for years can end up with a CLI several feature
releases ahead of its daemon without anyone noticing. docker version with
both blocks visible is the check.
Which component disturbs what
The operational core of the lesson:
| Component upgraded | Running containers | Networking | Builds | In-flight docker exec |
|---|---|---|---|---|
docker-ce-cli | Untouched | Untouched | Untouched | Untouched |
docker-compose-plugin | Untouched | Untouched | Untouched | Untouched |
docker-buildx-plugin | Untouched | Untouched | Interrupted | Untouched |
docker-ce (daemon) | Survive only with live restore | Rules re-applied | Interrupted | Dropped |
containerd.io | Survive β shims are separate processes | Untouched | Interrupted | Dropped |
The two rows worth memorising are the last two, and the asymmetry between them surprises people: upgrading containerd is less disruptive to running containers than upgrading the daemon, because the shim that supervises each container is a separate process that outlives a containerd restart, whereas containers die with the daemon unless live restore is enabled.
Knowledge check
Knowledge check Β· 4 questions
Q1. A runc CVE is announced. Which package version bump delivers the fix on a Debian or Ubuntu host?
Q2. What happens to running containers when only `containerd.io` is upgraded and containerd restarts?
Q3. Which packages can be upgraded without any risk to a running container? Select all that apply.
Q4. Upgrading containerd.io means every running container is immediately using the new shim and runc binaries.
Passing score: 75%. Answers are checked in this browser.