Skip to main content
Two layers protect application data. Longhorn S3 backups are the primary layer; a small restic CronJob covers what volume snapshots can’t capture consistently (Postgres logical dumps) plus legacy hostPath trees.

Longhorn S3 (primary)

A Longhorn RecurringJob snapshots every volume to S3 daily. The platform and bulk StorageClasses auto-enroll new volumes into that job, so any PVC is backed up without per-service wiring. Restore from the Longhorn UI (Backup → restore into a new PVC). Longhorn’s S3 target and the RecurringJob live in cluster/infra/longhorn/.

restic CronJob (logical dumps + legacy trees)

A single k8s CronJob (cluster/infra/backup/, image ghcr.io/amarcin/cluster-backup:9d5da0b) runs encrypted, deduplicated, incremental restic to S3 (prefix cluster):
It handles the two things a raw volume snapshot can’t:
  1. PostgreSQLpg_dumpall via kubectl exec into each DB pod using the pod’s own in-pod credentials (no DB password materialized outside the pod). Covers miniflux (reader ns) and temporal (infra ns).
  2. restic backup of those dumps plus the legacy config trees — the i3 hostPath tree (/home/user/apps/i3) and the local-path PVC root (/var/lib/rancher/k3s/storage).
The old SQLite .backup stages were removed — those apps’ volumes are covered by Longhorn S3 snapshots. Media is not backed up (re-downloadable). RESTIC_PASSWORD + AWS_* come from the SealedSecret backup-secrets; DB passwords are never stored in it. Pinned to i3 (kind: storage) because the legacy hostPath tree lives there.

Bootstrap secrets

Stored off-site (Apple Passwords / 1Password) — everything needed to restore from scratch:
  • Restic repository password
  • AWS credentials for the backup IAM user (bucket augustin-backups, scoped)
  • GitHub SSH key (to clone this repo to a replacement node)
  • The sealed-secrets sealing key’s age recipient private key (decrypts the monthly key backup — see recovery)