Skip to main content
RunBook Academy

Docker & ContainersXIII Β· RegistriesBackup and restore

Registry backup and restore

Advanced⏱ ~24 mindocker

What you'll learn

  • Enumerate everything a registry backup must contain for a restore to produce working pulls
  • Take a consistent backup of a live registry, or know why yours is not consistent
  • Restore and verify with a test whose failure is unambiguous
  • Explain why replication is not a backup

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-12

Not yet marked complete on this device.

The registry is the source of every production image. If it is lost, you cannot deploy and β€” worse β€” you cannot roll back. Treat it like the database it is.

Treating it like a database means being specific about what its state consists of, because the intuitive answer is wrong in a way that produces a backup which restores cleanly and then serves nothing.

What a backup must contain

A Distribution registry’s state is two trees and they are not interchangeable.

The blob store. Every layer, every image config, every manifest body, stored once each under a path derived from its own SHA-256. This is where essentially all the bytes are. It is also completely anonymous: nothing in it records that a particular blob is a layer of myapp:release-2026-08.

The naming tree. Under repositories/<name>/ sit the tag links (_manifests/tags/<tag>/current/link, a file whose contents are a digest), the manifest revision history, and the per-repository links to the blobs each repository references.

Beyond storage, a restore also needs:

  • The registry configuration. delete.enabled, the storage driver settings, the auth configuration, the HTTP secret. A restore against a default config produces a registry that refuses deletions and possibly rejects your clients.
  • TLS material, or a documented path to reissuing it.
  • Authentication state, wherever it lives β€” an htpasswd file, a token service configuration, or an external IdP registration.
  • For Harbor, Quay or GitLab: the product database, restored to a point consistent with the storage. Harbor’s own guidance covers the database volume and the registry storage together; check your version’s documentation for what else its data volume holds, because a database restored against freshly generated instance secrets can leave stored credentials unreadable.

The consistency problem

A tar of a live registry is not a snapshot. It reads the tree over minutes while clients are pushing, and the result depends on what happened during that window.

Cold backup

Configuration changestop, capture, start
STAMP=$(date +%F)
docker stop registry
tar czf "/backup/registry-${STAMP}.tar.gz" -C /srv/registry .
docker start registry

Pulls fail for the duration, which is usually acceptable overnight and never acceptable during business hours.

Read-only backup

Better: put the registry in maintenance read-only rather than stopping it. Pulls keep working β€” so deploys of existing images are unaffected β€” and no new content can arrive to tear the capture.

Configuration changeread-only for the duration
STAMP=$(date +%F)

docker rm -f registry
docker run -d --name registry --restart always -p 5000:5000 \
-e REGISTRY_STORAGE_MAINTENANCE_READONLY_ENABLED=true \
-v /srv/registry:/var/lib/registry registry:3

tar czf "/backup/registry-${STAMP}.tar.gz" -C /srv/registry .

docker rm -f registry
docker run -d --name registry --restart always -p 5000:5000 \
-v /srv/registry:/var/lib/registry registry:3

Snapshot backup

Best, where the storage supports it: an atomic snapshot, taken in milliseconds, with the archive built from the snapshot afterwards while the registry serves normally.

Configuration changeLVM snapshot
STAMP=$(date +%F)
sudo lvcreate --size 5G --snapshot --name registry-snap /dev/vg0/registry
sudo mkdir -p /mnt/snap
sudo mount -o ro /dev/vg0/registry-snap /mnt/snap

sudo tar czf "/backup/registry-${STAMP}.tar.gz" -C /mnt/snap .

sudo umount /mnt/snap
sudo lvremove -f /dev/vg0/registry-snap

Size the snapshot for the writes expected during the capture, not for the size of the registry. A snapshot that fills is dropped by the kernel and the backup silently becomes garbage.

Harbor and friends

Read-only / SafeHarbor database dump
STAMP=$(date +%F)
docker exec harbor-db pg_dump -U postgres registry > "/backup/harbor-db-${STAMP}.sql"

The registry storage and the database describe the same objects from two sides. Restoring a database from Tuesday against storage from Wednesday gives you a Harbor that lists artefacts it cannot serve and serves artefacts it does not list.

Restore

  1. Record a known digest before you start β€” one you can pull today β€” so the restore has something falsifiable to prove.
  2. Verify the archive reads end to end without extracting it.
  3. Extract to a new directory, not over the live one.
  4. Start a registry against the restored directory on a different port, leaving the original untouched.
  5. Pull the recorded digest from the restored registry, with no local copy present, so blobs are actually transferred.
  6. Compare the tag inventory against what you expect.
  7. Only then cut traffic over.
Read-only / Safeverify the archive before trusting it
ARCHIVE=/backup/registry-2026-08-08.tar.gz

gzip -t "$ARCHIVE" && echo 'gzip stream OK'
tar tzf "$ARCHIVE" > /dev/null && echo 'archive readable end to end'
tar tzf "$ARCHIVE" | grep -c 'docker/registry/v2/repositories/'
gzip stream OK
archive readable end to end
1483

Illustrative output

That last count is the check people miss. A nonzero number of entries under repositories/ is the evidence that the naming tree is in the archive at all β€” the difference between a backup and a pile of anonymous blobs.

Configuration changerestore beside, not over
ARCHIVE=/backup/registry-2026-08-08.tar.gz
RESTORE_DIR=/srv/registry-restore

sudo mkdir -p "$RESTORE_DIR"
sudo tar xzf "$ARCHIVE" -C "$RESTORE_DIR"

docker run -d --name registry-restore -p 5001:5000 \
-v "$RESTORE_DIR":/var/lib/registry registry:3
Read-only / Safethe verification that can fail
EXPECTED_DIGEST=sha256:REPLACE_ME
REF="localhost:5001/myorg/myapp@${EXPECTED_DIGEST}"

docker image rm "$REF" 2>/dev/null || true
if docker pull "$REF"; then
echo 'RESTORE OK: manifest resolved and all blobs transferred'
else
echo 'RESTORE FAILED: do not cut over' >&2
fi
RESTORE OK: manifest resolved and all blobs transferred

Illustrative output

Pulling by digest is what makes this a real test. Content addressing means the daemon verifies the bytes it received against the digest it asked for, so a successful pull is a cryptographic statement that the restored blobs are the original blobs β€” not merely that a file of the right size was present.

Read-only / Safeinventory comparison
RESTORED=http://localhost:5001

curl -fsS "$RESTORED/v2/_catalog?n=1000" \
| tr ',' '\n' | grep -c '"' || true

for repo in myorg/myapp myorg/myapi; do
printf '%s: ' "$repo"
curl -fsS "$RESTORED/v2/${repo}/tags/list" | tr ',' '\n' | grep -c '"' || true
done
42
myorg/myapp: 11
myorg/myapi: 9

Illustrative output

RPO in a registry is not RPO in a database

A 24-hour-old registry backup means up to 24 hours of pushed images are missing. That sounds like the database framing, and it is not quite, because most of those images are reproducible: the source is in version control, the pipeline is in version control, and rebuilding regenerates them.

The images that are genuinely lost are the ones you cannot rebuild:

  • built from a commit that has since been force-pushed away,
  • built with a dependency version that is no longer resolvable,
  • built by a pipeline whose configuration has since changed,
  • built from inputs that were never in version control at all.

So the useful question is not β€œhow many hours of images can we lose” but β€œhow many of the images on our current deploy path can we reproduce byte-for-byte today”. For most organisations the honest answer is fewer than they assume, and finding that out during a restore test is much cheaper than finding it out during a recovery.

Knowledge check

Knowledge check Β· 5 questions

  1. Q1. A backup captures only the blob store of a Distribution registry. What does a restore from it produce?

  2. Q2. You cannot take an atomic snapshot and must copy the two trees separately from a live registry. Which order is safer, and why?

  3. Q3. Which of these belong in a registry restore plan beyond the storage data? Select all that apply.

  4. Q4. S3 cross-region replication of the registry bucket is an adequate substitute for backups.

  5. Q5. Verifying a restore with a full `docker pull` by digest proves the restored blobs are byte-identical to the originals.

Passing score: 75%. Answers are checked in this browser.