add nightly Vault raft snapshot backup to R2

Snapshot via vault operator raft snapshot save using a least-privilege
periodic token. Stored in legion-vault-backup R2 bucket via restic.
Retains 30 daily / 12 weekly / 6 monthly snapshots. Runs at 3am,
offset from the Gitea backup at 2am.
This commit is contained in:
Will Anderson
2026-04-24 01:36:30 -05:00
parent 816d79760d
commit b1867a542d
2 changed files with 129 additions and 0 deletions
@@ -0,0 +1,94 @@
---
# Nightly Vault backup — raft snapshot → Cloudflare R2 via restic
# Schedule: 3am daily (offset from Gitea backup at 2am).
# Retains 30 daily / 12 weekly / 6 monthly snapshots.
apiVersion: batch/v1
kind: CronJob
metadata:
name: vault-backup
namespace: vault
spec:
schedule: "0 3 * * *"
concurrencyPolicy: Forbid
failedJobsHistoryLimit: 3
successfulJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
serviceAccountName: default
initContainers:
- name: vault-snapshot
image: hashicorp/vault:latest
command:
- /bin/sh
- -c
- |
set -e
echo "Taking Vault raft snapshot..."
VAULT_TOKEN=$VAULT_SNAPSHOT_TOKEN \
vault operator raft snapshot save /snapshot/vault.snap
echo "Snapshot complete, size: $(du -sh /snapshot/vault.snap)"
env:
- name: VAULT_ADDR
value: "http://vault.vault.svc.cluster.local:8200"
- name: VAULT_SNAPSHOT_TOKEN
valueFrom:
secretKeyRef:
name: vault-backup-credentials
key: VAULT_SNAPSHOT_TOKEN
volumeMounts:
- name: snapshot
mountPath: /snapshot
resources:
requests:
memory: 64Mi
cpu: 50m
limits:
memory: 256Mi
containers:
- name: backup
image: restic/restic:latest
command:
- /bin/sh
- -c
- |
set -e
# Init repo if first run
restic snapshots 2>/dev/null || restic init
echo "Running restic backup..."
restic backup \
/snapshot/vault.snap \
--tag vault \
--tag legion \
--host legion
# Prune old snapshots
restic forget \
--keep-daily 30 \
--keep-weekly 12 \
--keep-monthly 6 \
--prune
echo "Backup complete."
restic snapshots --latest 3
envFrom:
- secretRef:
name: vault-backup-credentials
volumeMounts:
- name: snapshot
mountPath: /snapshot
readOnly: true
resources:
requests:
memory: 128Mi
cpu: 100m
limits:
memory: 256Mi
volumes:
- name: snapshot
emptyDir: {}
@@ -0,0 +1,35 @@
---
# vault-backup-credentials — R2 keys + restic password + Vault snapshot token
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: vault-backup-credentials
namespace: vault
spec:
refreshInterval: 1h
secretStoreRef:
name: vault
kind: ClusterSecretStore
target:
name: vault-backup-credentials
creationPolicy: Owner
template:
data:
VAULT_SNAPSHOT_TOKEN: "{{ .snapshot_token }}"
RESTIC_PASSWORD: "vault-restic-2026"
AWS_ACCESS_KEY_ID: "{{ .r2_access_key_id }}"
AWS_SECRET_ACCESS_KEY: "{{ .r2_secret_access_key }}"
RESTIC_REPOSITORY: "s3:https://651161e0a3d321561b4c90b5bcd5f15b.r2.cloudflarestorage.com/legion-vault-backup"
data:
- secretKey: snapshot_token
remoteRef:
key: secret/data/vault/snapshot-token
property: token
- secretKey: r2_access_key_id
remoteRef:
key: secret/data/r2
property: access_key_id
- secretKey: r2_secret_access_key
remoteRef:
key: secret/data/r2
property: secret_access_key