pv-migrate is a CLI tool/kubectl plugin for moving Kubernetes
PersistentVolumeClaim data.
Its primary workflow is direct PVC-to-PVC migration, including in-namespace, cross-namespace, and cross-cluster copies. It can also back up PVC data to bucket storage and restore it later using S3-compatible storage, Azure Blob, GCS, or a custom rclone remote.
Warning
Heads up: this is a side project I maintain in my spare time. I might take a long time to look at issues or PRs, or not get to them at all. Sorry in advance, and thanks for understanding.
On Kubernetes, renaming a resource like a Deployment is usually just a manifest change.
Create the same object with a new name or namespace, apply it, and move on.
PVCs are different. The Kubernetes object is only the metadata. The real data lives in the storage backend.
pv-migrate moves that data. It can copy directly between PVCs, or use bucket
storage as a backup target or intermediate hop.
Copy data directly from one PVC to another. This is the core pv-migrate workflow and uses rsync-based strategies.
$ pv-migrate --source old-pvc --dest new-pvcSee PVC-to-PVC migration for strategies and examples.
Back up a PVC to object storage and restore it later. Use this for backups, one-off exports, or moves where direct cluster-to-cluster connectivity is awkward.
$ pv-migrate backup \
--source app-data \
--backend s3 \
--bucket pv-backups \
--name app-data-2026-04-11
$ pv-migrate restore \
--dest app-data-restore \
--backend s3 \
--bucket pv-backups \
--name app-data-2026-04-11See bucket backup and restore for backend options, object layout, raw rclone config mode, and permissions caveats.
➡️ You have a database that has a PersistentVolumeClaim db-data of size 50Gi.
Your DB grew over time, and you need more space for it.
You cannot resize the PVC because it doesn't support volume expansion.
Create a new, bigger PVC db-data-v2 and use pv-migrate to copy data from db-data to db-data-v2.
➡️ You need to move PersistentVolumeClaim my-pvc from namespace ns-a to namespace ns-b.
Create the PVC with the same name and manifest in ns-b and use pv-migrate to copy its content.
➡️ You are moving from one cloud provider to another,
and you need to move the data from one Kubernetes cluster to the other.
Just use pv-migrate to copy the data securely over the internet.
➡️ You need to change the StorageClass of a volume, for instance,
from a ReadWriteOnce one (like local-path) to a ReadWriteMany like NFS.
As the StorageClass is not editable, you can use pv-migrate to transfer
the data from the old PVC to the new one with the desired StorageClass.
➡️ You need to keep a PVC backup in object storage before a risky operation,
or to export PVC data out of the cluster for later restore.
Use pv-migrate backup to copy the volume into S3-compatible storage, Azure Blob, or GCS,
then pv-migrate restore when you need the data back.
➡️ You want scheduled PVC backups using Kubernetes-native building blocks.
Run pv-migrate backup from a CronJob and rely on bucket lifecycle rules or separate automation for retention.
➡️ Direct cluster-to-cluster connectivity is awkward, blocked, or temporary.
Back up the source PVC to a bucket, then restore from that bucket into the destination cluster.
- Supports in-namespace, in-cluster, and cross-cluster migrations
- Uses rsync over SSH with a freshly generated Ed25519 or RSA key pair each time to securely migrate the files
- Supports backing up PVC data to and restoring it from S3-compatible, Azure Blob, or GCS bucket storage
- Supports custom rclone remotes for backup/restore backends
- Lets you override rendered manifests, including images, affinity, and other Helm values
- Supports multiple migration strategies and falls back when needed:
- Mount both PVCs in a single pod (mount)
- ClusterIP service (clusterip)
- LoadBalancer service (loadbalancer)
- NodePort service (nodeport, opt-in)
- Local port-forward transfer (local, opt-in)
- Push mode (
--rsync-push) for when the source side cannot expose a service, e.g., behind a firewall or NAT - Detach mode (
--detach) for large transfers, so the job can keep running after the CLI exits - Customizable strategy order
- Supports arm32v7 (Raspberry Pi, etc.), arm64, and amd64
- Supports completion for popular shells: bash, zsh, fish, powershell
See docs/install.md for install options and shell completion setup.
See docs/usage.md for usage guides and command references:
See CONTRIBUTING for details.
