Why Back Up Persistent Volumes?
Kubernetes persistent volumes (PVs) store stateful data for databases, file uploads, and application state. Unlike stateless pods that can be recreated from images, losing a persistent volume means losing irreplaceable data. This guide covers multiple strategies for backing up PVs reliably.
Backup Strategy Overview
- Velero + Kopia — Full cluster backup including PVs for disaster recovery
- CSI Volume Snapshots — Storage-level snapshots, fastest but provider-dependent
- Application-level dumps — Database exports via CronJobs
Installing Velero
wget https://github.com/vmware-tanzu/velero/releases/download/v1.14.0/velero-v1.14.0-linux-amd64.tar.gz
tar xzf velero-v1.14.0-linux-amd64.tar.gz
sudo mv velero-v1.14.0-linux-amd64/velero /usr/local/bin/
cat > /tmp/velero-credentials << EOF
[default]
aws_access_key_id=YOUR_ACCESS_KEY
aws_secret_access_key=YOUR_SECRET_KEY
EOF
velero install \
--provider aws \
--plugins velero/velero-plugin-for-aws:v1.10.0 \
--bucket velero-backups \
--secret-file /tmp/velero-credentials \
--backup-location-config region=us-east-1,s3Url=https://s3.wasabisys.com \
--use-node-agent \
--default-volumes-to-fs-backup \
--uploader-type=kopia
Backing Up with Velero
# Backup namespace with PVs
velero backup create my-app-backup \
--include-namespaces production \
--default-volumes-to-fs-backup --wait
# Schedule daily backups
velero schedule create daily-production \
--schedule="0 2 * * *" \
--include-namespaces production \
--default-volumes-to-fs-backup --ttl 720h
Pod Annotations for Volume Selection
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
metadata:
annotations:
backup.velero.io/backup-volumes: data,config
spec:
containers:
- name: app
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: app-data-pvc
CSI Volume Snapshots
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: csi-snapclass
driver: your-csi-driver.example.com
deletionPolicy: Retain
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: db-snapshot-20260315
namespace: production
spec:
volumeSnapshotClassName: csi-snapclass
source:
persistentVolumeClaimName: postgres-data-pvc
Database CronJob Backup
apiVersion: batch/v1
kind: CronJob
metadata:
name: postgres-backup
namespace: production
spec:
schedule: "0 1 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: postgres:16
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
command:
- /bin/bash
- -c
- |
pg_dump -h postgres-svc -U app_user app_db | \
gzip > /backup/db-$(date +%Y%m%d).sql.gz
volumeMounts:
- name: backup-vol
mountPath: /backup
volumes:
- name: backup-vol
emptyDir:
sizeLimit: 10Gi
restartPolicy: OnFailure
Restoring from Velero
velero get backups
velero restore create --from-backup my-app-backup --wait
velero restore create --from-backup my-app-backup \
--namespace-mappings production:staging --wait
Best Practices
- Use Velero for full cluster DR and CSI snapshots for rapid PV rollbacks
- Always do application-level backups for databases alongside volume backups
- Test restores monthly in a separate namespace
- Use backup TTLs to auto-clean old backups
- Store backups in a different region than your cluster
- Encrypt backup data at rest