New to KubeDB? Please start here.
Milvus Storage Migration
This guide will show you how to use the KubeDB Ops-manager operator to migrate the persistent volumes of a Milvus database from one StorageClass to another.
Before You Begin
You should be familiar with the following
KubeDBconcepts:Complete the dependency setup from Prepare Dependencies. It installs MinIO and creates the object-storage secrets (
milvus-storage-configformilvus-standalone,milvus-cluster-storage-configformilvus-cluster).You need at least two
StorageClasses — the current one and the target one. This guide migrates fromlocal-pathtolonghorn-custom:$ kubectl get sc NAME PROVISIONER ALLOWVOLUMEEXPANSION AGE local-path (default) rancher.io/local-path false 11h longhorn-custom driver.longhorn.io true 11h
Note: The yaml files used in this tutorial are stored in docs/guides/milvus/storage-migration/yamls folder in GitHub repository kubedb/docs.
Storage Migration — Standalone Milvus
Deploy the standalone quickstart manifest, which uses local-path, and wait until it is Ready:
If you deleted a Milvus of the same name in an earlier guide, delete its object-storage prefix first, as shown in Prepare Dependencies. Otherwise the new database can hang in
Provisioning.
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.10.12/docs/guides/milvus/quickstart/yamls/standalone.yaml
milvus.kubedb.com/milvus-standalone created
$ kubectl get milvuses.kubedb.com -n demo milvus-standalone -w
$ kubectl get pvc -n demo -l app.kubernetes.io/instance=milvus-standalone
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-milvus-standalone-0 Bound pvc-... 1Gi RWO local-path 14m
Apply the StorageMigration OpsRequest
storage-migration-standalone.yaml
apiVersion: ops.kubedb.com/v1alpha1
kind: MilvusOpsRequest
metadata:
name: storage-migration
namespace: demo
spec:
type: StorageMigration
databaseRef:
name: milvus-standalone
migration:
storageClassName: longhorn-custom
oldPVReclaimPolicy: Delete
timeout: 10m
Here,
spec.migration.storageClassNameis the targetStorageClass.spec.migration.oldPVReclaimPolicycontrols what happens to the old PersistentVolume (DeleteorRetain).
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.10.12/docs/guides/milvus/storage-migration/yamls/storage-migration-standalone.yaml
milvusopsrequest.ops.kubedb.com/storage-migration created
Watch Progress
$ kubectl get milvusopsrequest storage-migration -n demo
NAME TYPE STATUS AGE
storage-migration StorageMigration Successful 87s
$ kubectl describe milvusopsrequest storage-migration -n demo
...
Status:
Conditions:
Message: StorageClass migration is in progress
Reason: Running
Type: Running
Message: pet set deleted; ConditionStatus:True; PodName:milvus-standalone
Type: PetSetDeleted--milvus-standalone
Message: get storage class; ConditionStatus:True
Type: GetStorageClass
...
Phase: Successful
During migration, the operator runs a migrator job to copy the data, recreates the PVC on the new StorageClass, and recreates the pod. A standalone Milvus has a single pod, so the database is unavailable while it is migrated (about a minute and a half in our test).
Verify the Migration
The PVC is now backed by longhorn-custom, and the database spec reflects the new class:
$ kubectl get pvc -n demo -l app.kubernetes.io/instance=milvus-standalone
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-milvus-standalone-0 Bound pvc-... 1Gi RWO longhorn-custom 33s
$ kubectl get milvuses.kubedb.com milvus-standalone -n demo -o jsonpath='{.spec.storage.storageClassName}'
longhorn-custom
Storage Migration — Distributed Milvus
For a distributed Milvus, migration targets the workloads that carry persistent storage — i.e. streamingnode. Point spec.databaseRef.name at milvus-cluster.
This example uses two streamingnode replicas on local-path. storage-migration/yamls/milvus-distributed.yaml is the distributed quickstart manifest with streamingnode.replicas: 2. Deploy it and wait until it is Ready:
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.10.12/docs/guides/milvus/storage-migration/yamls/milvus-distributed.yaml
milvus.kubedb.com/milvus-cluster created
$ kubectl get milvuses.kubedb.com -n demo milvus-cluster -w
The distributed OpsRequest reuses the name of the standalone one, so delete the standalone OpsRequest first:
$ kubectl delete milvusopsrequest -n demo storage-migration
storage-migration-distributed.yaml
apiVersion: ops.kubedb.com/v1alpha1
kind: MilvusOpsRequest
metadata:
name: storage-migration
namespace: demo
spec:
type: StorageMigration
databaseRef:
name: milvus-cluster
migration:
storageClassName: longhorn-custom
oldPVReclaimPolicy: Delete
timeout: 10m
The operator migrates the PVC of every streamingnode replica. Starting from local-path:
$ kubectl get pvc -n demo -l app.kubernetes.io/instance=milvus-cluster -o custom-columns=NAME:.metadata.name,SIZE:.status.capacity.storage,SC:.spec.storageClassName
NAME SIZE SC
data-milvus-cluster-streamingnode-0 1Gi local-path
data-milvus-cluster-streamingnode-1 1Gi local-path
Apply the OpsRequest. The streamingnode replicas are migrated one at a time, so one of them keeps running throughout; the other roles are not restarted:
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.10.12/docs/guides/milvus/storage-migration/yamls/storage-migration-distributed.yaml
milvusopsrequest.ops.kubedb.com/storage-migration created
After the migration completes:
$ kubectl get milvusopsrequest storage-migration -n demo
NAME TYPE STATUS AGE
storage-migration StorageMigration Successful 3m40s
$ kubectl get pvc -n demo -l app.kubernetes.io/instance=milvus-cluster -o custom-columns=NAME:.metadata.name,SIZE:.status.capacity.storage,SC:.spec.storageClassName
NAME SIZE SC
data-milvus-cluster-streamingnode-0 1Gi longhorn-custom
data-milvus-cluster-streamingnode-1 1Gi longhorn-custom
$ kubectl get milvuses.kubedb.com milvus-cluster -n demo -o jsonpath='{.spec.topology.distributed.streamingnode.storage.storageClassName}'
longhorn-custom
Both streamingnode PVCs are migrated.
Cleaning up
$ kubectl delete milvusopsrequest -n demo storage-migration
$ kubectl patch -n demo milvus.kubedb.com milvus-standalone -p '{"spec":{"deletionPolicy":"WipeOut"}}' --type="merge"
$ kubectl patch -n demo milvus.kubedb.com milvus-cluster -p '{"spec":{"deletionPolicy":"WipeOut"}}' --type="merge"
$ kubectl delete milvus.kubedb.com -n demo milvus-standalone milvus-cluster
$ kubectl delete ns demo
Next Steps
- Learn about volume expansion of a Milvus database.
- Detail concepts of Milvus object.
- Want to hack on KubeDB? Check our contribution guidelines.
































