type: decision
status: active
timestamp: 2026-06-20
tags: [backup, restic, backblaze, b2, github-actions, cron]
status: active
timestamp: 2026-06-20
tags: [backup, restic, backblaze, b2, github-actions, cron]
Backups — restic CLI in GH Actions cron, target Backblaze B2
Weekly encrypted restic backups to B2 via GH Actions Actions schedule, targeting a Backblaze B2 bucket. Locks the restic + B2 + GH Actions triple.
Backups — restic CLI in GH Actions cron, target Backblaze B2
Decision
The family’s backup architecture is the triple:
| Layer | Pick |
|---|---|
| Backup engine | restic (OSS, BSD-2-Clause) |
| Backup target | Backblaze B2 (free 10 GB + 3x egress) |
| Scheduler | GitHub Actions schedule (weekly Sunday 03:00 UTC) |
The full setup — workflow YAML, repo init, restore drill — is the
runbooks/security/restic-backup-setup.md
runbook. Retention policy is --keep-daily 7 --keep-weekly 4 --keep-monthly 12 (max 23 snapshots).
Why
- restic gives encryption + dedup + integrity check in one binary. AES-256 + Poly1305-AES + content-addressed chunks. No plugin surface to maintain. Single static binary drops into the runner.
- B2 is the only no-card S3-compatible target the family already
picked. Locked at
object-storage-split.md. 10 GB free- 3x stored egress covers many weeks of family-scale dedup’d backups.
- GH Actions schedule fits the job shape per
cron-split-cf-vs-gh.md. Build env, secrets surface, repo checkout — all already wired. Cloudflare Cron Triggers can’t run a static binary on disk. - All three layers are no-card / free-tier. Restic is OSS, B2 is
free at our scale, GH Actions is unlimited on public repos. No
exception to
rules/no-card-on-file.md.
Implications
- Each data-bearing repo carries its own
.github/workflows/backup-weekly.ymlper the per-repo CI rule. No central backup orchestrator. - Secrets —
RESTIC_PASSWORD,B2_KEY_ID,B2_APPLICATION_KEY,B2_BUCKET_NAME— all live in Doppler and sync to GitHub Secrets persecurity/secrets-management-doppler.md. - One bucket per repo to keep blast radius small; one password per bucket to keep rotation cost bounded.
- Restore drill is mandatory before declaring a repo’s backup loop “done” — see runbook step 4.
restic check --read-data-subset=5%runs in the same workflow so silent corruption surfaces by week 20 at latest.
Cross-refs
- restic service entry
- Backblaze B2 service entry
- GitHub Actions schedule
- Restic backup setup runbook
- Object storage split decision
- Cron split decision
- Doppler — secrets source-of-truth
- No card-on-file rule