korb moves a Kubernetes PVC to another StorageClass by running a copy job in the cluster
Move Kubernetes PVCs between Storage Classes and Namespaces
At a glance
- What is it?
- korb is a Go CLI for moving data between Kubernetes PersistentVolumeClaims, either to a different StorageClass or namespace, or out to a tar archive. It works by creating a temporary PVC and running a mover job inside the cluster, it picks a strategy automatically when only one fits, and every strategy warns that you must have stopped the pods using the data first.
- Who is it for?
- Adopt korb if you need to migrate a PersistentVolumeClaim to a different StorageClass or namespace and you can schedule a copy job in the cluster and stop the workload while it runs. Do not adopt it for a live database that cannot be quiesced, because every strategy warns that you must have stopped all pods accessing the data, and the copy happens through a pod that mounts the volume.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One command, one PVC, and a copy that runs inside the cluster
korb moves data from PVCs between StorageClasses, or renames them. The usage is short enough to state in full: korb [pvc [pvc]] [flags], so you name the PVC or the pair of PVCs and then set flags.
The example in the README shows the common case, moving one claim onto a different StorageClass:
~ ./korb --new-pvc-storage-class ontap-ssd redis-data-redis-master-0The design decision worth understanding is how the data actually moves. korb does not stream the volume contents over the network from your laptop. It creates a temporary PVC inside the cluster and starts a mover job as a pod, and that pod mounts both the source and the destination volume and copies the files between them. The tool then waits for the pod, cleans up the job, and continues with the next stage.
The log in the README makes the sequence visible, and the component tags help: the migrator component resolves the source PVC and the compatible strategies, the strategy component runs the stages, and the mover-job component watches the pod. The strategy selected in that run was copy-twice-name, and the stages are labelled from 1 through 6.
So the data path is entirely inside the cluster. That is what lets it move a volume between storage classes that have no shared access, since both ends are mounted into the same pod.
Strategies are pluggable, and korb picks one when only one fits
The strategy system is the part of the design that gives korb its flexibility. The --strategy flag selects one, and by default the tool will try to auto-select. The README points to the strategies directory in the repository and asks you to read the comments in the strategy source code for what each one does.
The auto-selection logic is visible in the log output. The tool lists the compatible strategies, and in the example there was only one, so it ran it. The copy-twice-name strategy is described in the log as copying the PVC to the new StorageClass and with new size and a new name, deleting the old PVC, and copying it back to the old name. That is the shape of a StorageClass migration where the final volume keeps the original name and identity, and the intermediate step is a throwaway.
The export strategy does something different, and the second example uses it explicitly:
~ ./korb overseerr-config --strategy exportIts description is to export the PVC content into a tar archive. The log for that run shows the interesting detail: the tool first reports that the strategy was not compatible, with the error that the expected import file overseerr-config.tar does not exist, and then lists the compatible strategies, which are the copy variant and export. Export is chosen by the user in that case.
The log lines carry structured fields rather than free text, with component, strategy, stage and phase attributes, which is what makes the output of a long running migration readable after the fact.
Every strategy warns you to stop the pods first
Both examples in the README emit the same warning before doing anything:
WARN[0000] This strategy assumes you've stopped all pods accessing this data. component=strategy strategy=copy-twice-nameThat single line is the most important thing to understand about korb. The copy runs in a pod that mounts the volume, and the tool has no way of knowing whether the workload that owns the data has stopped writing to it. If it has not, you get a copy of a directory that is changing underneath you, and the result is a corrupt volume that looks fine until the application reads it.
So the correct workflow is: stop the workload, confirm the pods are gone, run korb, start the workload again. The tool does not do that part for you, and the --force flag is explicitly described as ignoring the warning which would normally halt the tool during validation, which is a blunt instrument for exactly the mistake that causes data loss.
There is a related detail in the timeout handling. The log shows the tool setting the timeout from the PVC size, with a value of 8m0s for that particular claim. The --timeout flag overwrites the auto-generated value, and the default is 60s for the pod to start, while the copy timeout is based on the PVC size. For a large volume on slow storage, the auto-generated copy timeout is the one that will bite, and that is the value to override.
The --skip-pvc-bind-wait flag skips waiting for the PVC to be bound, which is useful when your provisioner binds slowly, and --tolerate-any-node allows the job to tolerate node taints, which matters when the source volume is only reachable from specific nodes.
The flags are the configuration surface, and defaults come from the source
Every new-PVC property defaults to the source PVC, which is the behaviour that makes the common case safe. The --new-pvc-name flag reuses the same name if empty, --new-pvc-size reuses the source size, --new-pvc-access-mode reuses the source access mode, --new-pvc-storage-class reuses the source StorageClass, and --new-pvc-namespace falls back to the namespace from your kubeconfig file. The --source-namespace flag works the same way, defaulting to the kubeconfig namespace.
So to move a volume to a different StorageClass and nothing else, you set one flag. The size and access mode formats follow the Kubernetes manifest conventions, with sizes in units like Gi and Ti, and access modes such as ReadWriteOnce and ReadWriteMany.
The --container-image flag points at the job image, which defaults to ghcr.io/beryju/korb-mover:v2. That image is the thing that actually copies the bytes, so it has to be pullable by your cluster, and if you run an air gapped environment you will need to mirror it.
The --kube-config flag takes an absolute path, and its default value in the help text is a path on the author's own machine, which is a small sign of where the help text was generated.
The repository is a Go module at beryju.org/korb/v2, requiring Go 1.26.0, and it depends on the Kubernetes client libraries, cobra for the command line, logrus for the structured logging you see in the output, a progress bar library for the file transfer progress, and a prefixer package. The Makefile builds a single binary and has a build-final target that cross compiles for linux arm, linux arm64, linux amd64 and darwin amd64.
Exporting to tar is the escape hatch from the cluster
The export strategy is the one that does not require a second volume, and the log of the second example shows what it produces. The mover job streams the contents out and the tool writes them into a tar archive locally, with a progress indicator and per-file download rates:
/source/
/source/db/
/source/db/db.sqlite3
/source/db/db.sqlite3-wal
/source/logs/overseerr.log
/source/logs/.20136e5b8544ec13The tar output even keeps the leading slash behaviour visible, with the standard tar message about removing leading / from member names, so the archive contains /source/ as its top level. The download lines show the file sizes and transfer speeds as they arrive, which for a large volume is the main feedback you get.
That combination of export and import is what makes korb a migration tool rather than a copy utility. The log for the export run shows the tool checking whether the expected import file overseerr-config.tar exists, and the error message tells you that the import side looks for a file named after the PVC. So a workflow of exporting on one cluster and importing on the other is anticipated by the design, not just implied by having an export strategy.
What is not documented is how the import is invoked, so if cross-cluster transfer is your goal you will need to read the strategy source in pkg/strategies, which the README points to as the place to look.
Where a Kubernetes-native move is the wrong tool
The tool does one thing, and that thing has hard preconditions. The data must be in a PVC, the workload must be stopped, and the cluster must be able to run a pod that mounts both volumes. A live PostgreSQL or Redis instance with an active writer does not meet the second condition, and no amount of flag tuning changes that.
The tools that exist for the live case take a different approach. A storage backend with its own snapshot and replication facility, or a database level logical dump and restore, both capture a consistent snapshot of a running system, which is exactly what the korb model cannot do. The README's own warning is the acknowledgement of that gap.
There is also a size and time limit worth thinking about. korb copies through a pod, so the copy is bounded by the cluster's network and disk throughput, and a multi terabyte volume becomes a multi hour operation with a job running the whole time. The auto-generated timeout scales with the PVC size, which suggests the author expects large volumes to be the normal case rather than an edge case, but the README does not document a resume mechanism, so a failed copy at hour three starts over.
The alternative to compare against is doing it by hand, which is a real option because the underlying operation is not complex. Create the destination PVC, run a copy pod with two volume mounts, wait, swap the workloads over. korb's value over that is the strategy handling, the timeout sizing, the cleanup of temporary resources and the structured logging, and its cost is that you are accepting a fixed copy mechanism rather than choosing your own tooling, for example a specialised data copy utility that can handle live or incremental transfers.
On maintenance, the last push was on 2026-10-19 and the repository is not archived, and the release list shows v2.3.4 on 2025-07-18, v2.3.3 on 2025-07-08 and v2.3.2 on 2024-06-05. The licence is GPL-3.0, which is a copyleft grant and a licensing note rather than legal advice, and it is worth reading carefully if you intend to redistribute a binary built from it.
Editorial conclusion
Adopt korb if you need to migrate a PersistentVolumeClaim to a different StorageClass or namespace and you can schedule a copy job in the cluster and stop the workload while it runs. Do not adopt it for a live database that cannot be quiesced, because every strategy warns that you must have stopped all pods accessing the data, and the copy happens through a pod that mounts the volume. Verify first that the pods using the PVC are stopped before you start, that the destination StorageClass allows the access mode and size you pass with the new-pvc flags, and that the copy job image ghcr.io/beryju/korb-mover:v2 can be pulled by your cluster.
Frequently asked questions
How do I install korb?
With Homebrew, run brew tap beryju/tap followed by brew install korb. Alternatively, download the binary of the latest release from the GitHub releases page. The project is written in Go and the Makefile has a build target that produces a single binary at bin/korb.
What happens when korb moves a PVC between StorageClasses?
It creates a temporary PVC and starts a mover job as a pod that mounts both volumes, copies the files, cleans up the job, then deletes the original PVC and creates the final destination PVC before a second copy back to the original name. The log for that run shows stages one through six with the strategy named copy-twice-name.
What do I have to do before running korb on a live workload?
Stop the pods that are using the data. Every strategy in the README logs a warning that the strategy assumes you have stopped all pods accessing this data, because the copy runs in a pod that mounts the volume and cannot tell whether the workload is still writing to it. The --force flag ignores the validation warning.
Can korb export a PVC to a file?
Yes, the export strategy exports the PVC content into a tar archive, and the README example streams the files out with a progress indicator. The log for that run also shows the tool checking for an expected import file named after the PVC, so an export and import pair is part of the design.
Which mover image does korb use and can I change it?
The --container-image flag selects the image used for the moving jobs and defaults to ghcr.io/beryju/korb-mover:v2. Since that image does the actual copying, it has to be pullable by your cluster, which matters in an air gapped environment.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/beryju-korb)