What this lab is
You will build three backup repositories from the same source data, restore a single file from each, and confirm the restored bytes match. The tools cover three different shapes of backup problem:
- rsync —link-dest — local filesystem snapshots that look like full backups but share hardlinks with previous days. Cheap, no encryption, no compression, no dedup across machines. Good for on-host backups of small datasets.
- Borg — encrypted, authenticated, content-defined-chunked backups with deduplication and compression. Good for offsite or remote backups of a single host to a remote server.
- Restic — also encrypted and content-defined-chunked, with
snapshots that copy-on-write, plus a built-in
checkthat walks the entire repository to verify integrity. Good for the same shape of problem as Borg, with a friendlier command line and built-in verification.
The lesson this lab reinforces: backups that are not exercised are backups that do not exist. The lab’s deliverable is a restore test, not a backup test.
Setup
A scratch directory with three files of different sizes and shapes:
SRC=/var/tmp/backup-lab
mkdir -p "$SRC"
# 50 MiB sparse file
truncate -s 50M "$SRC/big.bin"
# 200 small files
for i in $(seq 1 200); do echo "line $i" > "$SRC/small_$i.txt"; done
# A file we will modify between backups to test incremental behaviour
echo "version 1" > "$SRC/changelog.md"
du -sh "$SRC"
Step 1 — rsync —link-dest [UNVERIFIED]
The plain rsync -a SRC DEST/ shape is not a backup tool, it is a
copy tool. The lab’s first job is to replace that with the
--link-dest form so that yesterday’s data is shared with today’s
target via hardlinks.
BACKUP=/var/tmp/backup-rsync
mkdir -p "$BACKUP"
# First backup: a full copy.
rsync -a --link-dest="$BACKUP/last" "$SRC/" "$BACKUP/$(date +%F)/"
# Save the name for the next run.
ln -sfn "$(date +%F)" "$BACKUP/last"
# Modify a file and run a second backup.
echo "version 2" > "$SRC/changelog.md"
rsync -a --link-dest="$BACKUP/last" "$SRC/" "$BACKUP/$(date +%F)/"
ln -sfn "$(date +%F)" "$BACKUP/last"
Inspect:
# Each dated directory looks like a full backup.
ls "$BACKUP/2026-08-12"
ls "$BACKUP/2026-08-13"
# But the disk usage is roughly the size of one copy + the deltas.
du -sh "$BACKUP"/*
du -sh "$BACKUP"
# A file that did not change is hardlinked; inode count proves it.
ls -li "$BACKUP/2026-08-12/big.bin"
ls -li "$BACKUP/2026-08-13/big.bin"
# Both should share an inode number.
Step 2 — Borg [UNVERIFIED]
Borg is the right shape for a single host backing up to a remote server over SSH. It encrypts the data, deduplicates it across archives, and lets you prune old archives while keeping the ones that matter.
export BORG_REPO=/var/tmp/backup-borg
export BORG_PASSPHRASE='lab-only-do-not-reuse-in-production'
borg init --encryption=repokey "$BORG_REPO"
# First archive.
borg create --stats --list "$BORG_REPO::$(hostname)-$(date +%F-%H%M)" "$SRC"
# Modify a file and back up again.
echo "version 2" > "$SRC/changelog.md"
borg create --stats "$BORG_REPO::$(hostname)-$(date +%F-%H%M)" "$SRC"
borg list "$BORG_REPO"
Prune old archives. Keep the last 7 daily:
borg prune --keep-daily=7 --keep-weekly=4 --keep-monthly=6 "$BORG_REPO"
Restore:
# List the contents of an archive.
borg list "$BORG_REPO::$(hostname)-2026-08-12-1200"
# Extract one file.
borg extract "$BORG_REPO::$(hostname)-2026-08-12-1200" "changelog.md"
# Restore to a directory by changing into it first.
mkdir -p /tmp/borg-restore && cd /tmp/borg-restore
borg extract "$BORG_REPO::$(hostname)-2026-08-12-1200"
diff -r /tmp/borg-restore "$SRC" && echo "restore matches"
Step 3 — Restic [UNVERIFIED]
Restic covers the same shape of problem as Borg with a different
command surface. Its check is the differentiator: it walks the
repository and verifies every chunk, on demand.
export RESTIC_REPOSITORY=/var/tmp/backup-restic
export RESTIC_PASSWORD='lab-only-do-not-reuse-in-production'
restic init
restic backup "$SRC"
echo "version 2" > "$SRC/changelog.md"
restic backup "$SRC"
restic snapshots
Restore:
mkdir -p /tmp/restic-restore && cd /tmp/restic-restore
restic restore latest --target .
diff -r . "$SRC" && echo "restore matches"
# Restore a single file from a specific snapshot.
restic restore <snapshot-id> --target /tmp/restic-restore-file --include changelog.md
Verify:
restic check
restic check exits non-zero if any chunk is unreadable or its hash
does not match. Run it on a schedule.
Acceptance criteria
- Three backup trees produced, three restores verified by
diff -r. - rsync disk-usage evidence:
du -shfor thelasttree versus the sum of its dated copies. - Borg prune output recorded.
restic checkoutput recorded.- A short decision note: which tool fits the host’s backup shape,
and how often the maintainer will run
check/verify.