Skip to content

Config backups (Velero)

Velero backs up the *arr/Jellyfin/Seerr /config PVCs (Ceph RBD — SQLite state, history, quality profiles) to an external S3 bucket, daily, retained 14 days. It uses File System Backup (kopia via the node-agent) — no CSI snapshot CRDs required — and is opt-in: only the config volume is copied, so the 100 Ti NFS /data is skipped.

PieceWhere
Velero HelmRelease + node-agentk8s/manifests/base/cluster-services/velero/ (ns velero, Flux cluster-services-velero)
S3 credentialscloud-credentials — from Vault via vault-secrets-operator (velero-secrets role)
Backup target (BSL)velero-helm.yamlconfiguration.backupStorageLocation
Schedulevelero-schedules/schedule-media.yamlmedia-config-daily (daily 02:00, ttl: 336h) — applied by a separate Flux Kustomization (cluster-services-velero-schedules) that dependsOn the install, so the velero.io CRD exists before the Schedule is applied
Opt-in markerbackup.velero.io/backup-volumes: config on each media pod

1. Point the BackupStorageLocation at your S3 — edit velero-helm.yaml (bucket / region / s3Url are not secret). Current target is Backblaze B2:

backupStorageLocation:
- name: default
provider: aws
default: true
bucket: k8s-homelab-muehlena-de-velero-backup
config:
region: eu-central-003
s3Url: https://s3.eu-central-003.backblazeb2.com
s3ForcePathStyle: "true"
checksumAlgorithm: "" # non-AWS S3 (B2/MinIO/…) reject the x-amz-checksum header

2. Create the Vault policy + role (once), then write the S3 credentials to Vault. The velero-secrets role/policy are Terraform-managed like the other apps:

Terminal window
cd tf/application-parameters/hl-cluster-vault && terraform init -upgrade && terraform apply

Write the credentials as two readable fields — VSO’s transformation renders them into the cloud INI that Velero expects:

Terminal window
bao kv put secrets/cluster/internal/ns/velero/backup \
access_key_id=AKIA... \
secret_access_key=...

Commit the BSL edit. Flux installs Velero, VSO syncs cloud-credentials (the templated cloud key only — the raw fields are excluded), and the BSL goes Available (kubectl -n velero get secret cloud-credentials should have a cloud key).

Terminal window
kubectl -n velero get backupstoragelocation default # PHASE=Available
kubectl -n velero get schedule media-config-daily
velero backup create media-adhoc --from-schedule media-config-daily # or wait for 02:00
velero backup describe media-adhoc --details # PodVolumeBackups = the config volumes
Terminal window
# list, then restore the whole media namespace from a backup:
velero backup get
velero restore create --from-backup media-config-daily-20260712020000

Restore recreates the <app>-config PVCs and kopia-restores their contents. The static NFS media-data PV/PVC and the app manifests themselves come back from Git (Flux) — this backup is specifically the stateful config that isn’t reproducible from the repo.

  • DR scope: an external bucket means backups survive a full Ceph loss. Guard the bucket’s own credentials/retention accordingly.
  • On first reconcile the media-config-daily Schedule may error briefly until the Velero CRDs are installed by the HelmRelease — Flux retries, no action needed.
  • Bazarr/Seerr are included too (their /config is annotated), so subtitle/request state is captured alongside the *arr DBs.