Verify a Velero backup
A backup that has never been restored isn’t a backup. The
backup-verify operator runs scheduled or ad-hoc verification jobs
in two modes - cheap checksum-only and the real
full-restore mode that creates a sandbox namespace, applies the
Velero Restore CR, diffs the result against the backup manifest,
then tears the sandbox + Restore down.
This recipe walks through full-restore end-to-end.
Prerequisites
Section titled “Prerequisites”- ugallu umbrella chart installed with backup-verify enabled.
- Velero installed in
veleronamespace, with a workingBackupStorageLocation(“BSL”) and at least one completedBackup. - Permissions: the
backup-verifyServiceAccount must be able to CRUDRestoresin the velero namespace and read/delete the sandbox namespace cluster-wide. The umbrella chart provisions this RBAC by default.
kubectl -n velero get backups.velero.io# nightly-2026-04-29 Completed ...Create the run
Section titled “Create the run”The sandbox namespace name must end with -bvsandbox - a
ValidatingAdmissionPolicy enforces the suffix so accidental
full-restores can’t target a production namespace.
apiVersion: security.ugallu.io/v1alpha1kind: BackupVerifyRunmetadata: name: nightly-velero-fullrestore namespace: ugallu-systemspec: backend: velero backupRef: name: nightly-2026-04-29 namespace: velero mode: full-restore sandboxNamespace: nightly-2026-04-29-bvsandbox timeout: 10mkubectl apply -f bvr-nightly.yamlWatch the pipeline
Section titled “Watch the pipeline”kubectl -n ugallu-system get backupverifyruns -wYou should see the Phase walk through:
Pending -> Running -> SucceededWhile Running, the controller:
- creates a
velero.io/v1.RestoreCR inveleronamespace withnamespaceMappingaimed at the sandbox namespace - polls the Restore’s
phaseevery 10s until terminal (Completed,Failed,PartiallyFailed) - lists Pods, ConfigMaps, Secrets and ServiceAccounts in the sandbox and diffs counts against the Backup’s manifest
- emits
BackupVerifyCompleted(orBackupVerifyMismatchif any finding crosses high severity) - deletes the sandbox namespace and the Restore CR
Inspect the result
Section titled “Inspect the result”kubectl -n ugallu-system get backupverifyresult nightly-velero-fullrestore-result -o yamlKey fields:
status.worstSeverity- the headline grade. Anythingmediumor above is worth investigating.findings[]- per-finding details:code,severity,detail,evidence. Avelero-restore-completedfinding atinfoseverity means the restore ran clean.restoredObjectCount- how many K8s objects materialised in the sandbox.checksum- empty for full-restore mode (the diff is the validation, not a checksum).
Find the SecurityEvent
Section titled “Find the SecurityEvent”kubectl get securityevents -A -l \ ugallu.io/source=ugallu-backup-verify \ --sort-by=.metadata.creationTimestampThe SE class flips from Compliance to Detection if any finding
crosses high severity. That’s the signal that drives forensics
or paging - a clean run is Compliance / info, a real corruption
is Detection / critical.
Schedule it
Section titled “Schedule it”For nightly verification add a CronJob that creates a fresh CR each run (CRs are immutable after creation):
apiVersion: batch/v1kind: CronJobmetadata: name: nightly-bvr namespace: ugallu-systemspec: schedule: "30 2 * * *" jobTemplate: spec: template: spec: serviceAccountName: ugallu-backup-verify-cron restartPolicy: OnFailure containers: - name: kubectl image: bitnami/kubectl:1.31 command: - /bin/sh - -c - | TS=$(date -u +%Y%m%d%H%M%S) cat <<EOF | kubectl apply -f - apiVersion: security.ugallu.io/v1alpha1 kind: BackupVerifyRun metadata: name: nightly-${TS} namespace: ugallu-system spec: backend: velero backupRef: name: nightly-${TS} namespace: velero mode: full-restore sandboxNamespace: bv-${TS}-bvsandbox timeout: 15m EOF(Adjust the bitnami/kubectl image to your preferred mirror -
the project policy bans Bitnami in production.)
TTLConfig keeps BackupVerifyResult CRs for 30 days by default
- enough to walk back through the last month of nightly runs without disk pressure.