Docker & ContainersXXIX Β· Docker UpgradesPre-upgrade
Pre-upgrade capture β what to save and what it is actually worth
What you'll learn
- Capture the five artefacts a Docker host cannot be rebuilt without
- Record running container specifications in a form you can act on
- Judge when a `/var/lib/docker` copy is worth taking and when it is theatre
- Verify the capture before starting the upgrade rather than after
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
The instinct before an upgrade is to βtake a backupβ, and the instinct is
right. The usual execution is wrong: people either take nothing, on the grounds
that the containers are stateless, or they try to snapshot
/var/lib/docker, which is large, slow, inconsistent while the daemon runs,
and of limited use afterwards.
What you actually need is five small artefacts. They fit in a few kilobytes and they are what turns βthe upgrade broke the hostβ into βrebuild it from theseβ.
The five artefacts
| Artefact | Why | Size |
|---|---|---|
daemon.json and systemd drop-ins | The daemon will not behave the same without them | bytes |
| Compose files and deploy manifests | The declarative source of every container | kilobytes |
| Image digest inventory | Lets you re-pull byte-identical images | kilobytes |
| Container specification dumps | Reconstructs anything not in a Compose file | kilobytes |
| Volume data backup | The only irreplaceable item | large |
Only the last one is a backup in the ordinary sense, and it belongs to the backup part of this course rather than to the upgrade procedure. The other four are configuration capture, and they are cheap enough that there is no excuse for skipping them.
Capturing them
#!/usr/bin/env bash
set -euo pipefail
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
OUT="/var/backups/docker-preupgrade/$STAMP"
mkdir -p "$OUT"
# 1. Daemon configuration and any unit customisation
cp -a /etc/docker/daemon.json "$OUT/" 2>/dev/null || echo 'no daemon.json' > "$OUT/daemon.json.absent"
systemctl cat docker.service > "$OUT/docker.service.txt"
systemctl cat containerd.service > "$OUT/containerd.service.txt"
# 2. Component versions, so you know what you are rolling back to
docker version > "$OUT/version.txt"
docker info > "$OUT/info.txt"
dpkg -l 'docker-*' 'containerd*' > "$OUT/packages.txt"
# 3. Image inventory with digests
docker image ls --digests --no-trunc --format '{{.Repository}}:{{.Tag}} {{.Digest}} {{.ID}}' > "$OUT/images.txt"
# 4. Full specification of every container, running or not
docker ps -a --format '{{.Names}}' > "$OUT/container-names.txt"
docker ps -aq | xargs -r docker inspect > "$OUT/containers.json"
# 5. Volume and network inventory (not the data - see below)
docker volume ls --format '{{.Driver}} {{.Name}}' > "$OUT/volumes.txt"
docker network ls --format '{{.Driver}} {{.Name}}' > "$OUT/networks.txt"
# 6. Compose projects, if the host uses them
docker compose ls --format json > "$OUT/compose-projects.json" 2>/dev/null || true
echo "capture written to $OUT"
ls -la "$OUT"containers.json is the important one and the one people leave out. It is the
complete resolved specification of every container: image digest, environment,
mounts, networks, resource limits, restart policy, labels. If the host has
containers created by hand rather than by Compose β and almost every host has
at least one β this file is the only record of how they were built.
The image digest inventory is the valuable one
$ docker image ls --digests --format '{{.Repository}}:{{.Tag}} {{.Digest}}'registry.example.com/myapp:release-2026-08 sha256:9f2b4d6e8c0a2f4b6d8e0c2a4f6b8d0e2c4a6f8b0d2e4c6a8f0b2d4e6c8a0f2b
registry.example.com/myapi:release-2026-08 sha256:3c5e7a9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a3c5e7b9d1f3a5c
postgres:16.14-bookworm sha256:7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a3c5e7b9dIllustrative output
Digests, not tags. A tag is a pointer that can move; the digest is the image.
Recovering with docker pull myapp:release-2026-08 gets you whatever that tag
points at today. Recovering with the digest gets you the bytes that were
running before the upgrade:
docker pull registry.example.com/myapp@sha256:9f2b4d6e8c0a2f4b6d8e0c2a4f6b8d0e2c4a6f8b0d2e4c6a8f0b2d4e6c8a0f2bwhile read -r ref digest _; do
case "$digest" in
sha256:*) ;;
*) echo "SKIP $ref (no digest - built locally?)"; continue ;;
esac
repo="${ref%%:*}"
if docker manifest inspect "$repo@$digest" > /dev/null 2>&1; then
echo "OK $ref"
else
echo "FAIL $ref - not retrievable from registry"
fi
done < /var/backups/docker-preupgrade/latest/images.txtAny FAIL line is a decision to make before the upgrade, not after: push the
image, or accept that it is not recoverable and plan accordingly.
docker manifest inspect is still labelled experimental in the CLI help. It
works and it is the shortest way to ask a registry whether a digest exists,
but if you would rather not depend on an experimental subcommand,
docker pull --quiet against the digest on a throwaway host answers the same
question at the cost of downloading the layers. crane manifest and
skopeo inspect are the non-Docker equivalents.
What a /var/lib/docker copy is worth
# Stop the daemon AND the socket, or systemd restarts it on the next API call
sudo systemctl stop docker.socket docker.service
sudo systemctl stop containerd.service
# Now the tree is quiescent - snapshot at the filesystem layer
sudo lvcreate --snapshot --size 20G --name docker-preupgrade /dev/vg0/docker
sudo systemctl start containerd.service
sudo systemctl start docker.servicedocker.socket before docker.service is not optional. The socket unit
activates the daemon on demand, so stopping only the service leaves systemd
ready to start it again the moment anything touches /var/run/docker.sock β
including your own monitoring agent.
Knowledge check
Knowledge check Β· 4 questions
Q1. Which capture artefact reconstructs a container that was created by hand rather than by Compose?
Q2. Why record image digests rather than tags in the pre-upgrade inventory?
Q3. Which statements about copying /var/lib/docker before an upgrade are correct? Select all that apply.
Q4. Stopping only `docker.service` before a snapshot is insufficient, because `docker.socket` can activate the daemon again on the next API call.
Passing score: 75%. Answers are checked in this browser.