Skip to main content
RunBook Academy

Docker & ContainersXXIX Β· Docker UpgradesPre-upgrade

Pre-upgrade capture β€” what to save and what it is actually worth

Intermediate⏱ ~22 mindocker

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

Not yet marked complete on this device.

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

ArtefactWhySize
daemon.json and systemd drop-insThe daemon will not behave the same without thembytes
Compose files and deploy manifestsThe declarative source of every containerkilobytes
Image digest inventoryLets you re-pull byte-identical imageskilobytes
Container specification dumpsReconstructs anything not in a Compose filekilobytes
Volume data backupThe only irreplaceable itemlarge

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

Read-only / Safepre-upgrade capture
#!/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

Read-only / Safedigest inventory
$ 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:7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a3c5e7b9d

Illustrative 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:

Service impact possiblerecover by digest
docker pull registry.example.com/myapp@sha256:9f2b4d6e8c0a2f4b6d8e0c2a4f6b8d0e2c4a6f8b0d2e4c6a8f0b2d4e6c8a0f2b
Read-only / Safeverify recoverability
while 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.txt

Any 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

Service impact possibleconsistent snapshot
# 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.service

docker.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

  1. Q1. Which capture artefact reconstructs a container that was created by hand rather than by Compose?

  2. Q2. Why record image digests rather than tags in the pre-upgrade inventory?

  3. Q3. Which statements about copying /var/lib/docker before an upgrade are correct? Select all that apply.

  4. 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.