Skip to main content
RunBook Academy

CephCVI · RBD BackupRBD Backup

Incremental export with export-diff

Advanced⏱ ~18 minrbd

What you'll learn

  • Take an incremental export
  • Maintain the snapshot chain it depends on
  • Reconstruct a full image from a chain
  • Handle a broken chain

Prerequisites

None — start here.

Verified against Ceph Tentacle 20.2.x · Ceph Squid 19.2.x (supported previous) · cephadm matches the verified Ceph release · podman 4.x · csi-rbd and csi-cephfs current · RBD / CephFS / RGW current (matches Ceph release) · Linux kernel 5.15+ (5.10 minimum) · Ubuntu 24.04 LTS (Ceph host baseline) · Debian 12 (Bookworm) (Ceph host baseline) · Rocky Linux / RHEL / AlmaLinux 9.x (Ceph host baseline) · Proxmox VE 9.x (cross-course integration) · Kubernetes 1.31+ (cross-course integration) · 2026-08-18

Not yet marked complete on this device.

Why this matters in production

Full exports do not fit nightly windows past a certain size, and incremental export is what makes off-cluster backup sustainable.

Taking an incremental

# Substitute your own values before running:
POOL=rbd-vms
IMAGE=vm-disk-01

# the base, once
rbd snap create "$POOL/$IMAGE@base"
rbd export-diff "$POOL/$IMAGE@base" /backups/image01-base.diff
# Substitute your own values before running:
POOL=rbd-vms
IMAGE=vm-disk-01

# each subsequent run
rbd snap create "$POOL/$IMAGE@$(date +%Y%m%d)"
rbd export-diff --from-snap base \
  "$POOL/$IMAGE@20260818" /backups/image01-20260818.diff
ls -lh /backups/image01-*.diff
The diff contains only the objects that changed between the two
snapshots, which for a typical VM is a small fraction of the image.
# Substitute your own values before running:
POOL=rbd-vms
IMAGE=vm-disk-01

# chained: each night's diff is from the previous night
rbd export-diff --from-snap 20260817 \
  "$POOL/$IMAGE@20260818" /backups/image01-20260818.diff
StrategyDiff sizeRestore complexity
Always from basegrows over timebase + one diff
Chained from previoussmall and constantbase + every diff in order
Periodic new baseresets bothbase + diffs since that base
Chained diffs are smallest and make restore depend on every link. A
weekly full or new base bounds that dependency.

Maintaining the chain

The snapshot named in --from-snap must still exist on the source image.
Removing it breaks the next incremental.
POOL=rbd-vms
IMAGE=vm-disk-01
rbd snap ls ${POOL}/${IMAGE}
# Substitute your own values before running:
POOL=rbd-vms
IMAGE=vm-disk-01

# retain at least the snapshot the next incremental will reference
rbd snap ls "$POOL/$IMAGE" --format json | python3 -c '
import sys,json
s = sorted(json.load(sys.stdin), key=lambda x: x["id"])
print("newest (next --from-snap):", s[-1]["name"] if s else "none")
print("removable:", [x["name"] for x in s[:-2]])'
# Substitute your own values before running:
POOL=rbd-vms
IMAGE=vm-disk-01

# remove older ones, keeping the most recent two
rbd snap ls "$POOL/$IMAGE" --format json | python3 -c '
import sys,json
s = sorted(json.load(sys.stdin), key=lambda x: x["id"])
for x in s[:-2]: print(x["name"])' | \
while read snap; do rbd snap rm "$POOL/$IMAGE@$snap"; done

Reconstructing

# create the target and apply the base
SIZE=100G
POOL=rbd-vms
rbd create --size ${SIZE} ${POOL}/image01-restored
rbd import-diff /backups/image01-base.diff ${POOL}/image01-restored
POOL=rbd-vms

# then every diff, in order
for d in $(ls -1 /backups/image01-2026*.diff | sort); do
  echo "applying $d"
  rbd import-diff "$d" "$POOL/image01-restored" || { echo "FAILED at $d"; break; }
done
POOL=rbd-vms

rbd snap ls "$POOL/image01-restored"
rbd info "$POOL/image01-restored"
import-diff refuses to apply a diff whose starting snapshot is not
present on the target, which is what makes out-of-order application
fail loudly rather than silently corrupting.

A broken chain

BreakRecovery
A diff file lost or corruptrestore to the last good diff; take a new full export
The --from-snap snapshot removed on sourcetake a new full export and start a new chain
A diff applied out of orderimport-diff refuses; apply in order
The target divergedrestore to a fresh image, not the diverged one
# verify the chain applies before relying on it
SIZE=100G
POOL=rbd-vms
rbd create --size ${SIZE} ${POOL}/chain-test
for d in $(ls -1 /backups/image01-*.diff | sort); do
  rbd import-diff "$d" ${POOL}/chain-test || { echo "CHAIN BROKEN at $d"; break; }
done
rbd rm ${POOL}/chain-test

Quiz

Knowledge check · 4 questions

  1. Q1. Why take a periodic new full export when using chained diffs?

  2. Q2. Diffs can be applied to the target in any order.

  3. Q3. Set up incremental backup for an RBD image.

    A 4 TiB image cannot complete a full export in the nightly window. The team wants incrementals.

  4. Q4. What must remain on the source image for the next incremental to work?

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

Production discipline

Alert on any failed nightly export-diff — a silent gap breaks every subsequent diff and only surfaces during a restore. Take a new full base periodically to bound both the restore step count and how much a single lost file invalidates.

Cross-course references

  • Kubernetes: incremental volume snapshots carry the same chain dependency
  • Linux: incremental backup chains fail at their weakest link, silently until restore