How to back up a Kubernetes cluster with Velero
Velero copies a cluster's Kubernetes objects into an object storage bucket, and its persistent volumes either as snapshots or by copying their files with Kopia. Install it with velero install, schedule backups with velero schedule create, and restore with velero restore create --from-backup. Everything below was checked against the Velero v1.18 documentation (current release v1.18.4) and velero-plugin-for-aws v1.14.4.
What Velero backs up
Each backup has two parts. Kubernetes objects, from Deployments to Secrets and PersistentVolumeClaims, are read through the Kubernetes API and uploaded to your bucket as a tarball. Volume data is handled in one of three ways:
| Method | How it works | Where the data ends up |
|---|---|---|
| Provider snapshots | A volume snapshotter plugin asks the cloud to snapshot each disk, for example Amazon EBS through the AWS plugin | With the cloud provider, in the disk's region |
| CSI snapshots | Velero creates CSI VolumeSnapshots. Needs --features=EnableCSI and a CSI driver that supports snapshots | In the storage system, unless you also move the data with --snapshot-move-data |
| File System Backup | A node-agent DaemonSet reads each pod volume's files and uploads them with Kopia | In your bucket, deduplicated and incremental |
On a self-managed cluster without a snapshot-capable CSI driver, File System Backup is the practical choice, and it keeps volume data in the same bucket. The examples below use it.
A Velero backup is not atomic: objects created or changed while it runs may be missed. File System Backup copies live files, so a database volume can be caught mid-write; use a backup hook (below) or snapshots for databases. hostPath volumes are not supported; local persistent volumes are.
Install the CLI
The velero CLI runs on your workstation and uses your kubeconfig, like kubectl. On Linux, download the release and its checksums, verify, and install:
curl -fsSLO https://github.com/velero-io/velero/releases/download/v1.18.4/velero-v1.18.4-linux-amd64.tar.gzcurl -fsSLO https://github.com/velero-io/velero/releases/download/v1.18.4/CHECKSUMsha256sum --check --ignore-missing CHECKSUMtar -xvf velero-v1.18.4-linux-amd64.tar.gzsudo install velero-v1.18.4-linux-amd64/velero /usr/local/bin/velerovelero version --client-onlyUse arm64 in the file names on ARM machines. On macOS, brew install velero installs it.
Prepare the bucket and credentials
Velero needs a private bucket, ideally one per cluster, and an access key that can read, write, list and delete objects in it. S3-compatible services work through the AWS plugin. To create a bucket and key, see the guides for AWS S3, Cloudflare R2, Backblaze B2, Wasabi and DigitalOcean Spaces.
[default]
aws_access_key_id=YOUR_ACCESS_KEY_ID
aws_secret_access_key=YOUR_SECRET_ACCESS_KEYvelero install stores this file in a Secret named cloud-credentials in the velero namespace. Keep the keys somewhere safe outside the cluster: restoring into a new cluster needs them.
Install Velero in the cluster
velero install targets your current kubectl context, so check it with kubectl config current-context first. Then:
velero install \
--provider aws \
--plugins velero/velero-plugin-for-aws:v1.14.4 \
--bucket my-cluster-backups \
--prefix prod \
--secret-file ./credentials-velero \
--backup-location-config region=us-east-1,s3ForcePathStyle="true",s3Url=https://s3.example.com \
--use-volume-snapshots=false \
--use-node-agent \
--wait--provider awsselects the AWS object store plugin, which speaks the S3 API.--pluginsis the plugin image. Plugin v1.14.x pairs with Velero v1.18.x; check the compatibility table in the plugin's README when you upgrade.--bucketis the bucket.--prefixis optional and puts everything under a folder, so several clusters can share one bucket.--secret-fileis the credentials file above.--backup-location-configholds the S3 settings: theregion, your provider's endpoint ass3Url, ands3ForcePathStyle="true"for path-style URLs, which self-hosted services such as MinIO need.--use-volume-snapshots=falseskips the snapshot location, since this setup copies files instead.--use-node-agentinstalls the node-agent DaemonSet that File System Backup needs.--waitreturns only when the Velero deployment is ready.
On AWS itself, drop s3Url and s3ForcePathStyle. To use EBS snapshots there, replace --use-volume-snapshots=false with --snapshot-location-config region=us-east-1.
The plugin's README lists Backblaze B2 and Ceph as rejecting uploads with XAmzContentSHA256Mismatch unless you add checksumAlgorithm="" to --backup-location-config, which stops the plugin sending its default CRC32 checksum.
Check that the pods run and that Velero can reach the bucket:
kubectl -n velero get podsvelero backup-location getThe PHASE column must say Available. Unavailable means the bucket, endpoint, region or keys are wrong, and kubectl -n velero logs deployment/velero says which.
Choose which volumes to copy
File System Backup copies only the pod volumes you select. There are two ways to select them:
- Opt-in, the default: annotate pods with
backup.velero.io/backup-volumesand the names of the volumes to copy, as written in the pod spec. - Opt-out: add
--default-volumes-to-fs-backuptovelero backup create, or tovelero installto make it the default. Every pod volume is copied except service account tokens, Secrets, ConfigMaps, hostPath volumes, and volumes you list inbackup.velero.io/backup-volumes-excludes.
An annotation added to a running pod disappears when its Deployment or StatefulSet replaces the pod, so put it in the pod template. Changing the template restarts the pods:
spec:
template:
metadata:
annotations:
backup.velero.io/backup-volumes: dataFor a database, add a pre-backup hook that writes a dump into the volume, so Velero copies a clean file alongside the live data files. The command is a JSON array and runs without a shell unless you start one, and hooks time out after 30 seconds unless you set a timeout:
spec:
template:
metadata:
annotations:
backup.velero.io/backup-volumes: data
pre.hook.backup.velero.io/container: postgres
pre.hook.backup.velero.io/command: '["/bin/sh", "-c", "pg_dump -U postgres -Fc -f /var/lib/postgresql/data/velero.dump postgres"]'
pre.hook.backup.velero.io/timeout: 10mBack up
velero backup create shop-first --include-namespaces shop --default-volumes-to-fs-backup --waitshop-firstis the backup's name.--include-namespaces shoplimits the backup to one namespace. Leave it out to back up all namespaces and cluster-scoped objects;--exclude-namespacesskips the ones you list.- With a namespace filter, cluster-scoped objects such as StorageClasses and ClusterRoles are left out, apart from related ones like the PersistentVolumes of included claims. Add
--include-cluster-resources=trueto include every cluster-scoped object. --default-volumes-to-fs-backupcopies every pod volume. Leave it out if you annotate instead.--waitblocks until the backup finishes.
Then check it:
velero backup describe shop-first --detailsLook for Phase: Completed; PartiallyFailed means some items or volumes failed. The pod volume backups section lists each volume Kopia copied, so a volume missing there was never selected. velero backup logs shop-first prints the full log. Both commands fetch from the bucket through temporary signed URLs, so your machine must be able to reach the bucket's endpoint.
Schedule backups with retention
velero schedule create shop-daily --schedule="0 3 * * *" --include-namespaces shop --default-volumes-to-fs-backup --ttl 168h0m0s--scheduletakes a five-field cron expression in UTC, here 03:00 daily.@every 6halso works, andCRON_TZ=Europe/Berlin 0 3 * * *sets a time zone.--ttl 168h0m0skeeps each backup for seven days; the default is 30 days. Expired backups are deleted from the bucket with their snapshots, and File System Backup data is cleared later by repository maintenance.- The
velero backup createfilters work here too.
Scheduled backups are named after the schedule plus a timestamp, such as shop-daily-20261003030000. Trigger one now to test the schedule, then list your schedules:
velero backup create --from-schedule shop-dailyvelero schedule getDelete backups with velero backup delete NAME. kubectl delete backup removes only the Kubernetes object and leaves the files in the bucket.
Velero encrypts File System Backup data with a static, common key unless you change it, so anyone who can read the bucket can read the data. Keep the bucket private. To use your own password, set the repository-password key of the velero-repo-credentials Secret before the first backup; changing it later cuts Velero off from older backups.
Restore
Velero never overwrites what is in the cluster: existing objects are skipped, and ServiceAccounts are merged. To test a backup on a live cluster, restore into a new namespace:
velero restore create shop-test --from-backup shop-first --namespace-mappings shop:shop-restore-test --wait--namespace-mappings old:new renames the namespace on the way in. Claims are created fresh and provisioned by their StorageClass, and an init container holds each pod until its data is copied back. Check the result, then delete the test namespace:
velero restore describe shop-test --detailsLook for Phase: Completed and read the warnings and errors; already exists only means an object was skipped. velero restore logs shop-test has the full log.
For a real recovery, delete the broken namespace, then restore it:
kubectl delete namespace shopvelero restore create --from-backup shop-first --wait--from-schedule shop-daily restores a schedule's latest successful backup instead. --include-namespaces and --include-resources restore part of a backup, and --existing-resource-policy=update updates existing objects instead of skipping them, on a best-effort basis.
Restore into a new cluster
Point kubectl at the new cluster and run the same velero install command, with the same bucket, prefix, credentials, plugin and namespace. Then make the location read-only, so the new cluster cannot delete or add backups while you work:
kubectl patch backupstoragelocation default --namespace velero --type merge --patch '{"spec":{"accessMode":"ReadOnly"}}'Velero syncs the backup list from the bucket every minute. When velero backup get shows your backup, restore it with velero restore create --from-backup as above. Patch accessMode back to ReadWrite once the new cluster takes over.
If the new cluster's StorageClasses have different names, restored claims stay Pending. Map old names to new ones before restoring, with a ConfigMap in the velero namespace; the labels must be exactly these:
apiVersion: v1
kind: ConfigMap
metadata:
name: change-storage-class-config
namespace: velero
labels:
velero.io/plugin-config: ""
velero.io/change-storage-class: RestoreItemAction
data:
local-path: do-block-storagekubectl apply -f change-storage-class.yamlThe new cluster must run the same Kubernetes version or newer. Cloud snapshots cannot move between providers, or between regions with the AWS and Azure plugins; File System Backup data can, because it lives in the bucket. If you set your own repository password, set the same one here before restoring.
Common errors
| Symptom | Fix |
|---|---|
velero backup-location get shows Unavailable | Wrong bucket, endpoint, region or keys. kubectl -n velero logs deployment/velero names the problem. |
XAmzContentSHA256Mismatch in the logs | The storage rejects the plugin's checksum. Add checksumAlgorithm: "" under spec.config with kubectl -n velero edit backupstoragelocation default. |
Backups stay in New | The Velero server is not running. Check kubectl -n velero describe pods and the deployment's logs. |
A backup is stuck InProgress after Velero restarted | Velero cannot resume it. Delete it with kubectl -n velero delete backup NAME and run it again. |
| No volume data in the backup | No node agent (--use-node-agent), or the volume was not selected: annotate it or use --default-volumes-to-fs-backup. |
Restored pods stay in Init | They are waiting for their volume data. Check kubectl -n velero get podvolumerestores -l velero.io/restore-name=NAME; the node agent must run in this cluster. |
Restored claims stay Pending | Their StorageClass does not exist in this cluster. Map it with the change-storage-class ConfigMap. |
SignatureDoesNotMatch from velero backup logs | The storage must accept signature version 4 signed URLs. On Ceph, use a native Ceph account. |
Set the schedule from the data loss you can accept: see RPO and RTO explained. A bucket with Object Lock keeps a compromised cluster from deleting its own backups: see protecting backups from ransomware.
Frequently asked questions
- Does Velero back up etcd?
- No. Velero reads objects through the Kubernetes API and handles volume data with snapshots or File System Backup. It does not copy etcd's data files.
- How long does Velero keep backups?
- 30 days by default. Set --ttl on velero backup create or velero schedule create, for example --ttl 168h0m0s for seven days.
- Can I restore a Velero backup to a different cluster?
- Yes. Install Velero in the new cluster against the same bucket and prefix, wait for the backup list to sync, and run velero restore create. The new cluster needs the same Kubernetes version or newer.
- Is Velero's file system backup safe for databases?
- Not on its own. It copies live files, so a database can be caught mid-write. Add a pre-backup hook that dumps the database into the volume, or use snapshots.
- Does Velero work with S3-compatible storage?
- Yes, through the AWS plugin with s3Url and region in the location config. Velero's docs say these services are reported working by users, not tested regularly by the Velero team.
How this was checked
Commands, limits and prices were checked against these official pages, on October 3, 2026:
- Velero v1.18 docs: How Velero works
- Velero v1.18 docs: Basic install
- Velero v1.18 docs: Customize installation
- Velero v1.18 docs: File System Backup
- Velero v1.18 docs: Backup hooks
- Velero v1.18 docs: Backup reference (schedules, deletion)
- Velero v1.18 docs: Resource filtering
- Velero v1.18 docs: Restore reference
- Velero v1.18 docs: Cluster migration
- Velero v1.18 docs: Disaster recovery
- Velero v1.18 docs: Container Storage Interface snapshot support
- Velero v1.18 docs: Troubleshooting and debugging installation
- Velero v1.18.4 release and CLI source (command flags)
- Velero plugin for AWS: README (compatibility, install, known issues)
- Velero plugin for AWS: BackupStorageLocation config