Why I Finally Swapped dd for restic After Years — and What It Saved Me (419 GB, If You're Asking)
Sometimes the biggest problem isn't that a backup doesn't work — it's that it's been working for so long nobody notices how wastefully expensive it's become.
A backup that worked, it just happened to be enormous
The home server has had a weekly backup for years. It started out as an image of a small SD card — dd, simple, reliable, nothing fancy. The problem crept in quietly over time: the SD card eventually got swapped for a bigger SSD, but the backup method stayed the same — dd kept imaging the entire disk, now 238 GB, from scratch on every run, regardless of how much had actually changed since the previous week.
The result: 140 to 164 GB of compressed image per week, three retained backups at once eating 419 GB on the backup drive combined. For an image whose content barely changed from one week to the next.
What finally forced the issue
The trigger wasn't the backup failing — it came out of a side investigation (why the data drive was so slow under load), which revealed that the Sunday backup run overlapped in time with another regular maintenance job on the same drive — two heavy, simultaneous operations hitting the same spot at the same time.
That turned into an opportunity to fix both problems at once: size and timing.
Why restic, not just "a smaller dd backup"
The fix wasn't making the dd backup smaller (say, by skipping part of the disk) — it was switching to a tool that backs up differently. restic is a single static binary (no sudo apt install, no system dependencies needed) that backs up at the file level with deduplication — every run only writes to disk what actually changed, not the whole image from scratch again.
Instead of imaging the entire disk, the new backup was scoped: the root filesystem, Docker volumes, and a selection of important data (a deliberate decision to skip bulk media — it can be re-downloaded if ever needed, it isn't unique data). For the note-taking app's database (Joplin), the backup also switched from copying the live database's raw files to a logical pg_dumpall export — a consistent snapshot, instead of the risk of catching a write mid-flight.
A bug that showed up on the very first run — and why it's worth learning from
The first test run of the new script ran under a regular user account and produced dozens of "permission denied" errors — on Docker volumes, SSH keys, VPN configuration, the mail server's TLS certificates. Not a bug in restic itself — those files simply belong to root, and a regular account has no access to them, whereas the old dd approach (imaging the entire block device) never had to deal with this question at all, since it copied the disk at a level that bypasses file permissions completely.
Moving to a system-level root cron job (/etc/cron.d/, not a regular user's personal crontab) fixed everything at once — the second run had zero errors. An interesting side detail also confirmed that deduplication was working exactly as intended: the second run (this time complete, with none of the files missing from permission errors) only added 534 MB of new data to the backup — everything else was identical to what had already been captured, partially, by the first, incomplete run.
Trust isn't verified by assumption — it's verified by an actual restore
A backup that's never been tested by restoring it isn't a verified backup — it's just a hope. Part of the migration was therefore a real test: a restic restore of specific files, followed by a diff against the originals. A match, no differences.
The result, in numbers
Before: 140 to 164 GB per week, 419 GB across three retained backups, a run taking upwards of three hours. After: roughly 11 GB total thanks to deduplication, the first full run in about ten minutes. The backup drive's usage dropped from 87% to 71% the same day (including deleting old, no-longer-needed dd images). The run's schedule was also moved out of collision with the other maintenance job.
The last old dd backup wasn't deleted right away — it stayed as a safety net until the new system was fully verified through both backup and restore, and only then got removed.
The second layer: keeping the 3-2-1 rule intact through the switch
Cloud backup isn't new in this setup — I've followed the 3-2-1 rule (at least three copies of data, on two different media, one off-site) plus a cold copy on the side for a long time, and the weekly upload to cloud storage had been part of the chain well before the switch to restic. The problem was narrower, but real: that existing upload script was written specifically for the old dd format (it uploaded opi_backup_*.img.gz) — after switching to restic, it would have silently stopped uploading anything meaningful, since the new system doesn't produce that kind of file at all anymore. A quiet, unnoticed failure of the off-site copy would have been worse than not changing anything at all.
Instead of patching the old script, a second, independent restic repository was set up directly in the cloud storage (over the existing connection, no new tool needed), with a script that copies snapshots between the local and remote repository while keeping the same retention policy — specifically so that, even after changing the local mechanism, the same rule that's governed backups all along would keep holding.
What to take away
- "It's been working for years" isn't the same as "it's still correctly sized." The backup method chosen for a small SD card stayed unchanged even after being swapped for a disk ten times bigger — nobody asked whether it still made sense until it started colliding with something else.
- A deduplicated, file-level backup can be an order of magnitude smaller than a repeated full-disk image — especially when most of the data doesn't change between runs at all.
- Changing your backup method can expose hidden assumptions the old method never had to face — here, specifically, file permissions, which
ddat the block-device level simply bypassed. - A backup without a tested restore is just an assumption. An actual
restoreplus a comparison against the original is the only way to know a backup genuinely works, not just that it runs without an error message. - Changing a mechanism can silently undermine a rule that otherwise works fine long-term. The 3-2-1 rule (more copies, a different medium, an off-site copy) already applied here — the risk was that the old upload script was tied to the old file format, and a silent format change would have made it stop doing anything useful, with no error to flag it.
Backups are one of those homelab things that are easiest to leave running unnoticed for years — which is exactly why it's worth occasionally asking whether they're still doing their job the best way currently available.
Dealing with something similar?
This is exactly the kind of work I take on for others too — Linux servers, Docker deployments, backups, and secure access setup.
View Linux & Docker services