restic in practice: repository, snapshots, restore
File backup with deduplication and encryption: init, backup, snapshots, restore, forget with prune, and check.
Why restic
Server files (user uploads, configs, database dumps) need to go somewhere,
and plain rsync falls short here: it doesn't keep version history,
doesn't deduplicate, and doesn't encrypt anything.
restic does all of this out of the box. It splits data into blocks and stores each unique block once. Everything is encrypted on the client side. It can write to a local folder, over SFTP, to S3, and compatible storage. borg has the same set of properties; choosing between them is a matter of taste.
You can install restic yourself following the official instructions, but you don't need to for this lesson's labs: restic is already in the container.
Repository
A repository is a folder or bucket that holds encrypted blocks and metadata. You create it once:
export RESTIC_PASSWORD='a-long-password'
restic -r /backup/repo init
created restic repository 823f8ee661 at /backup/repo
Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.
The warning is literal. The password is the encryption key, and support
can't recover it for you. Keep the password in a secrets manager, and in
scripts pass it through a file and the RESTIC_PASSWORD_FILE variable. The
repository path can also be set with the RESTIC_REPOSITORY variable, so
you don't need the -r flag.
First backup
restic -r /backup/repo backup /var/lib/app
no parent snapshot found, will read all files
Files: 3 new, 0 changed, 0 unmodified
Dirs: 3 new, 0 changed, 0 unmodified
Added to the repository: 192.082 KiB (191.578 KiB stored)
processed 3 files, 190.040 KiB in 0:00
snapshot a304a096 saved
190 KiB read, and about the same written: the repository is empty, there's nothing to save on yet. The result of a backup is called a snapshot, and each one has its own ID.
Second backup: deduplication shows up
Add one line to the small users.csv file and repeat the command:
Files: 3 new, 0 changed, 0 unmodified
Dirs: 3 new, 0 changed, 0 unmodified
Added to the repository: 2.067 KiB (1.397 KiB stored)
processed 3 files, 190.048 KiB in 0:01
snapshot 2f2cff34 saved
190 KiB processed again, but only about 2 KiB added to the repository. The identical blocks are already there, and nobody writes them a second time. That's why thirty daily snapshots take up only a bit more than one.
The blocks themselves are large: restic splits files into chunks from 512 KiB to 8 MiB. A file smaller than 512 KiB becomes a single block, so any edit to it writes the whole thing again. On small files the savings come from the files that didn't change.
A snapshot stays self-contained: you can restore any of them, no need to assemble a chain.
The parent snapshot gotcha
Look at the two lines above again: no parent snapshot found and 3 new
instead of 1 changed. The files didn't change, but restic reread all of
them.
The reason is that restic looks for the previous snapshot, the parent, by host and paths. And the run happened in containers, where the host name is new every time:
ID Time Host Tags Paths Size
----------------------------------------------------------------------------------
a304a096 2026-10-09 17:57:55 287cc06da6db /var/lib/app 190.040 KiB
2f2cff34 2026-10-09 17:58:04 4b8743661a94 /var/lib/app 190.048 KiB
Fixed with the --host flag and a consistent name:
restic -r /backup/repo backup --host web-01 /var/lib/app
The first run with the new host name still won't find a parent: there are
no snapshots with web-01 yet. From the second run on, things look normal:
using parent snapshot 3d090790
Files: 0 new, 1 changed, 2 unmodified
Dirs: 0 new, 3 changed, 0 unmodified
Added to the repository: 2.081 KiB (1.413 KiB stored)
processed 3 files, 190.062 KiB in 0:00
snapshot d1b9b6b2 saved
One file changed, two untouched. Deduplication worked even without this, but extra disk reads get expensive on large volumes.
Listing snapshots and restoring
restic -r /backup/repo snapshots
restic -r /backup/repo restore d1b9b6b2 --target /restore
restoring snapshot d1b9b6b2 of [/var/lib/app] at 2026-10-09 17:58:16.305728838 +0000 UTC by root@web-01 to /restore
Summary: Restored 6 files/dirs (190.062 KiB) in 0:00
Instead of the latest snapshot's ID, restic also accepts the word latest.
Restoring puts data inside the given --target with the full original path
underneath it (/restore/var/lib/app/...). The original is left untouched,
which is the right call: it's easier to work things out on a copy.
Pulling out a single file without unpacking everything:
restic -r /backup/repo restore d1b9b6b2 --target /restore --include /var/lib/app/app.conf
Summary: Restored 4 / 1 files/dirs (26 B / 26 B) in 0:00
This kind of selective restore covers the most common request: "bring back
the one config I broke yesterday." If you just need to look at a file's
content from a snapshot, restic dump <ID> /var/lib/app/app.conf is enough:
it prints the file to stdout. And restic diff <ID1> <ID2> shows which
files changed between two snapshots.
Retention: forget and prune
Retention is the set of rules for how many snapshots to keep. forget
removes records of snapshots by rule, prune clears out blocks nobody
references anymore. They're usually run together:
restic -r /backup/repo forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
There's a non-obvious behavior waiting here. The simple command
restic -r /backup/repo forget --keep-last 2 --prune
on the repository of four snapshots from above left four, not two.
restic groups snapshots by host and paths (--group-by host,paths by
default) and applies the rule inside each group. There are three groups
here: two random container names with one snapshot each, and web-01 with
two. Each group kept up to two snapshots.
In practice that's convenient: a "7 days" policy applies separately to each
server. But if you expected exactly two snapshots across the whole
repository, the result is surprising. Change this with the --group-by ''
flag.
Before running this for real, --dry-run is worth using: it shows what
would be deleted without touching anything.
Checking
A copy only counts as a copy after it's been checked, and restic has a command for that:
restic -r /backup/repo check --read-data
load indexes
check all packs
check snapshots, trees and blobs
[0:00] 100.00% 4 / 4 snapshots
read all data
[0:00] 100.00% 8 / 8 packs
no errors were found
Without --read-data, only the repository's structure gets checked:
indexes, snapshots, and trees. That's fast. With the flag, restic reads and
verifies every data block. On terabytes that's slow and expensive in
bandwidth, so a full check usually runs on a monthly schedule, with a
partial one (--read-data-subset=1/10) more often.
check verifies the repository's integrity, not whether the data is usable.
A real test is restoring it and running whatever got restored.
What this looks like in production
#!/bin/sh
set -e
export RESTIC_REPOSITORY=s3:s3.example.com/backups
export RESTIC_PASSWORD_FILE=/etc/restic/pass
pg_dump -Fc mydb > /var/backups/mydb.dump
restic backup --host web-01 /var/backups /var/lib/app
restic forget --host web-01 --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
restic check
The script runs from cron, its output goes to a log, and the log feeds
monitoring. Separately watch for whether the backup ran at all: silence in
the logs usually means not "everything's fine" but "the script didn't run."
The --host flag on forget scopes retention to that server's snapshots,
so a single host's script doesn't clean out other hosts' snapshots in a
shared repository.
Common mistakes
Lost the password. The password is the encryption key. Lose it, and the data is gone for good. Keep a copy of the password separate from the server you're backing up.
No consistent --host. In containers and on machines that get
recreated, restic doesn't find the parent snapshot and rereads everything.
The sign in the output: no parent snapshot found on every run.
Restored the whole snapshot when you needed one file. A full restore
over the working folder also rolls back anything that changed since the
backup. For a single file, there's --include and restic dump.
forget left more snapshots than expected. The rule applies inside the
"host plus paths" group. Check the Host column in restic snapshots and
verify the policy with --dry-run.
check without restoring. check --read-data confirms the repository
is intact, not that a working application will come up from it.
What you'll need for the tasks
restic init,restic backup --host <name> <path>restic snapshotsandrestic snapshots --host <name>- the
using parent snapshot/no parent snapshot foundandAdded to the repositorylines inbackupoutput restic diff <ID1> <ID2>,restic dump <ID> <file path>restic restore <ID> --target <folder> --include <path>- the
RESTIC_REPOSITORYandRESTIC_PASSWORD_FILEvariables: already set in the labs, so you don't need-r
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson backup-basics-08-restic
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress