Keep It Stupid Simple Backup Strategy
A shell script so simple I'm willing to trust it with my most precious data.
The more critical a system is, the simpler it should be. In practice, that means building on battle-tested foundations and adding only the thin layer of logic that’s strictly necessary.
My home server’s backup is a good example. I aim to use SQLite wherever I can, but my self-hosted stack has grown to include several Postgres instances, Redis, message queues, and more. This couldn’t stop me from wanting Time Machine-style backups with granular file history, which means backing up often: hourly, in my case. Shutting down the stack every hour would’ve greatly inconvenienced its active user base of two, my partner and me, so I chose to back up everything live.
I could have gone down the rabbit hole of coordinating dedicated backup tooling: a bespoke method for every service, each with its own failure modes. That would have been anything but simple. Instead, I rely on the databases’ battle-tested crash-recovery paths.
With that assumption, the backup container needs just a simple shell script. All container volumes live in the same Btrfs1 subvolume, so the script can take an atomic snapshot of all state. It then feeds the snapshots into Borg2, which creates complete logical archives while deduplicating their chunks, so only new or changed chunks need storing and transferring to local or SSH backup targets. A database restored from one of these backups is in a state indistinguishable from that left by a power cut. The backup script supplies only the plumbing: configuring Btrfs and Borg, handling failures gracefully, and exposing a health-check status when human intervention is needed.
I’ve been using this since 2023. I’ve restored various files and folders and performed two full (albeit planned) disaster recoveries. Nothing builds confidence like a restore that works: Borg, Btrfs, and this little script have worked well, and I don’t expect to migrate away any time soon.