·7 min read·infrastructure
Share

Your Sovereign Stack Needs a Backup Plan: 3-2-1 and Restore Drills on One Mac

Self-hosting means owning your recovery. Here's the 3-2-1 backup rule, safe SQLite snapshots, encrypted offsite copies, and the restore drill everyone skips.

The day I realized "it's all on one Mac" was a threat, not a flex

I run three production Next.js sites off a single Mac Studio, behind one Cloudflare tunnel, orchestrated by launchd. A CyberPower Windows box handles GPU work. The crown jewel is a 299MB SQLite file — atlas.db, 154 tables — that holds years of tagged content, fan history, and revenue attribution I can never regenerate. For a long time the whole thing lived in exactly one place: an internal SSD.

That's not sovereignty. That's a single point of failure wearing a sovereignty costume. Owning your infrastructure means owning your recovery, and recovery is a thing you build on purpose, not a thing you hope for after the drive clicks. This post is the backup discipline I wired around my stack, and the part almost everyone skips: actually running a restore.

The 3-2-1 rule, translated for a self-hoster

The rule is old and boring and correct: 3 copies of anything you care about, on 2 different kinds of media, with 1 copy offsite. For a one-Mac operation that maps cleanly:

  • Copy 1 — the live data on the internal SSD (this is production, not a backup).
  • Copy 2 — a local snapshot on a separate physical disk. An external SSD or a NAS counts. The point is a different device so one dead drive doesn't take both.
  • Copy 3 — an encrypted push to cheap object storage, off the premises. If the apartment floods or the machine walks, this is what's left.

Two media, one offsite. That's the entire spec. Everything below is just doing it honestly.

What actually needs backing up (and what doesn't)

The mistake is backing up everything, which is expensive and slow and makes you skip it. Back up only what you can't rebuild:

  • Databases. atlas.db and its siblings. Irreplaceable.
  • Content archives. Masters, generated media, the stuff that took real work or real money to produce.
  • Config and dotfiles. ~/.cloudflared/config.yml, my launchd plists in ~/Library/LaunchAgents/, espanso configs, .gitconfig. Small, and reconstructing them from memory at 3am is misery.
  • Secrets. API keys, tokens — encrypted, always, never in plaintext offsite.

What I deliberately do not back up: node_modules, build output, model weights I can re-download, anything already in a git remote. It's reproducible. Backing it up just pads the bill and slows the restore. My rule of thumb: if git clone plus a build script can regenerate it, it doesn't go in the backup.

Snapshotting a live SQLite database without corrupting it

Here's the trap that bites people. atlas.db is being written to constantly. If you cp atlas.db backup/ mid-write, you can capture a half-committed page and get a backup that opens fine and is silently corrupt. cp is not atomic against an active writer.

SQLite ships the correct tool for this — the .backup command, which takes a consistent snapshot while writers keep going:

sqlite3 ~/daj-ai-hub/of-automation/data/qos/atlas.db \
  ".backup '/Volumes/backup-ssd/atlas-$(date +%F).db'"

That produces a clean, transactionally-consistent copy. Then I verify it before trusting it:

sqlite3 /Volumes/backup-ssd/atlas-2026-07-11.db "PRAGMA integrity_check;"
# expect: ok

If that doesn't print ok, the backup is garbage and I want to know now, not during a restore. A backup you haven't integrity-checked is a rumor.

Archive before delete — the habit that saves you between backups

Backups run on a schedule. Mistakes happen between schedules. So the cheapest insurance I have is a rule I follow by reflex: never delete, archive. Anything I'm about to remove gets moved into a dated folder first:

mkdir -p ~/.archive/$(date +%F)
mv sketchy-thing ~/.archive/$(date +%F)/

It has bailed me out more times than the real backups have, because it catches the "oops I ran that on the wrong file" class of failure that a nightly snapshot won't help with until tomorrow. Reversible operations only. Delete is forever; move is Tuesday.

Pushing an encrypted offsite copy

The offsite copy leaves my machine, so it leaves encrypted. I never hand a plaintext database to a third-party bucket. Encrypt locally, then sync:

# encrypt with a passphrase only I hold
gpg --symmetric --cipher-algo AES256 \
  /Volumes/backup-ssd/atlas-2026-07-11.db

# push the encrypted blob to object storage
rclone copy /Volumes/backup-ssd/atlas-2026-07-11.db.gpg \
  r2:my-backups/atlas/ --transfers 4

Cheap object storage (Cloudflare R2, Backblaze B2, S3 — pick one) runs pennies a month for a few gigs. The .gpg extension is the whole security model: even if the bucket is breached, they get ciphertext. Keep the passphrase somewhere that is not the same bucket, or you've built a very safe way to lock yourself out.

The part everyone skips: run the restore drill

A backup you have never restored is not a backup. It's a hypothesis. The only way to know it works is to rebuild from it, on purpose, before you're forced to.

Once a month I do a cold restore to a throwaway location and prove the data is real:

# pull the offsite copy back
rclone copy r2:my-backups/atlas/atlas-2026-07-11.db.gpg /tmp/drill/

# decrypt
gpg --decrypt /tmp/drill/atlas-2026-07-11.db.gpg > /tmp/drill/atlas.db

# prove it opens, is intact, and has the rows I expect
sqlite3 /tmp/drill/atlas.db "PRAGMA integrity_check;"
sqlite3 /tmp/drill/atlas.db "SELECT count(*) FROM media_lists;"

If the row count matches production, the passphrase works, and integrity is ok, then I actually have a backup. The first time I ran this drill I found my offsite copy was a week stale because a launchd job had silently died — exactly the kind of thing you want to discover during a drill instead of during a disaster. Time the whole restore too: knowing it takes eleven minutes to be back online changes how calmly you handle the bad day.

Owning your infra is the fun part. Owning your recovery is the part that lets you keep owning your infra.

FAQ

How often should I back up a live database like this?

Match the frequency to how much data you're willing to lose. For atlas.db, which changes all day, I run the .backup snapshot nightly via launchd and push offsite right after. If losing a full day would hurt, tighten to hourly. The snapshot is cheap and non-blocking, so frequency is bounded by storage cost and offsite bandwidth, not by risk to the live DB.

Why not just use Time Machine and call it done?

Time Machine is a fine local copy — it can be your copy #2 on separate media. But it doesn't satisfy 3-2-1 on its own: it's one destination, usually onsite, and it happily backs up a SQLite file mid-write, so you can get corrupt captures. Pair it with an explicit .backup snapshot for databases and an encrypted offsite push, and now you have real coverage.

Is encrypting the offsite copy really necessary for my own data?

Yes. The moment data leaves your machine for someone else's servers, you've expanded who can potentially read it. gpg --symmetric costs one command and turns a bucket breach into a non-event. The only new risk is losing the passphrase, so store it separately from the backup — a password manager, not the same bucket.

What's the single most common backup failure?

Silent staleness. A scheduled job dies, the last good copy is from three weeks ago, and nobody notices until restore day. The restore drill is the only reliable catch — when you actually pull and rebuild, a stale or broken backup is obvious immediately. A backup system you never test is just a folder that makes you feel safe.

Follow Hellcat Blondie everywhere

OnlyFans, Instagram, TikTok, and more. One page, all links.

Related