New to KubeDB? Please start here.
Neo4j StorageClass Migration
This guide shows how to migrate the StorageClass of a KubeDB-managed Neo4j cluster using Neo4jOpsRequest with type: StorageMigration.
Before You Begin
- You need a Kubernetes cluster and
kubectlconfigured. - Install KubeDB operator following setup guide.
- Ensure at least two
StorageClassresources are available in your cluster.
Use a dedicated namespace for this walkthrough:
$ kubectl create ns demo
namespace/demo created
Prepare Neo4j Database
First, verify available storage classes:
$ kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
custom-longhorn driver.longhorn.io Delete WaitForFirstConsumer true 3h38m
local-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 4h26m
longhorn (default) driver.longhorn.io Delete Immediate true 3h43m
longhorn-static driver.longhorn.io Delete Immediate true 3h43m
We will deploy Neo4j with local-path, then migrate to custom-longhorn.
Both old and new PVCs should stay on the same node. If the old class uses
WaitForFirstConsumer, use a new class withWaitForFirstConsumeras well.
Apply the Neo4j database manifest:
$ cat <<'EOF' | kubectl apply -f -
apiVersion: kubedb.com/v1alpha2
kind: Neo4j
metadata:
name: neo4j-test
namespace: demo
spec:
replicas: 3
deletionPolicy: WipeOut
version: "2025.12.1"
storage:
storageClassName: "local-path"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
EOF
neo4j.kubedb.com/neo4j-test created
$ kubectl get neo4j,pvc -n demo
NAME VERSION STATUS AGE
neo4j.kubedb.com/neo4j-test 2025.12.1 Ready 2m
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/data-neo4j-test-0 Bound ... 2Gi RWO local-path 2m
persistentvolumeclaim/data-neo4j-test-1 Bound ... 2Gi RWO local-path 2m
persistentvolumeclaim/data-neo4j-test-2 Bound ... 2Gi RWO local-path 2m
The database is Ready and all the PersistentVolumeClaim uses local-path StorageClass, Let’s create a database and seed some data.
Step 3 — Create a Database and Seed Data
Open a shell into pod neo4j-test-0 and run the following Cypher commands to:
- Create a new database called
appdb - Insert 2,000 test
Usernodes - Verify the count
# Retrieve the admin password
PASS=$(kubectl get secret -n demo neo4j-test-auth \
-o jsonpath='{.data.password}' | base64 -d)
# Create the database
$ kubectl exec -n demo neo4j-test-0 -- \
cypher-shell -u neo4j -p "$PASS" \
"CREATE DATABASE appdb IF NOT EXISTS WAIT"
# Seed 2,000 User nodes
$ kubectl exec -n demo neo4j-test-0 -- \
cypher-shell -d appdb -u neo4j -p "$PASS" \
"UNWIND range(1,2000) AS i CREATE (:User {id:i, name:'user-'+toString(i)})"
# Confirm the count
$ kubectl exec -n demo neo4j-test-0 -- \
cypher-shell -d appdb -u neo4j -p "$PASS" \
"MATCH (u:User) RETURN count(u) AS totalUsers"
Expected output:
totalUsers
2000
Apply StorageMigration OpsRequest
To migrate StorageClass, create a Neo4jOpsRequest:
$ cat <<'EOF' | kubectl apply -f -
apiVersion: ops.kubedb.com/v1alpha1
kind: Neo4jOpsRequest
metadata:
name: storage-migration
namespace: demo
spec:
type: StorageMigration
databaseRef:
name: neo4j-test
migration:
storageClassName: custom-longhorn
oldPVReclaimPolicy: Delete
timeout: 3000s
EOF
neo4jopsrequest.ops.kubedb.com/storage-migration created
Here,
spec.typemust beStorageMigration.spec.databaseRef.namepoints to target Neo4j database.spec.migration.storageClassNameis the destinationStorageClass.spec.migration.oldPVReclaimPolicycontrols old PV reclaim policy.
To retain old PVs after migration, use
oldPVReclaimPolicy: Retain.
Verify StorageClass Migration
Watch the OpsRequest status:
$ kubectl get neo4jopsrequest -n demo -w
NAME TYPE STATUS AGE
storage-migration StorageMigration Successful 8m
Check PVC storage class after migration:
$ kubectl get pvc -n demo
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-neo4j-test-0 Bound ... 2Gi RWO custom-longhorn 14m
data-neo4j-test-1 Bound ... 2Gi RWO custom-longhorn 14m
data-neo4j-test-2 Bound ... 2Gi RWO custom-longhorn 14m
The PVCs now use custom-longhorn, which confirms successful StorageClass migration.
$ PASS=$(kubectl get secret -n demo neo4j-test-auth -o jsonpath='{.data.password}' | base64 -d)
$ kubectl exec -n demo neo4j-test-0 -- \
cypher-shell -d appdb -u neo4j -p "$PASS" \
"MATCH (u:User) RETURN count(u) AS totalUsers"
totalUsers
2000
From the above output we can verify that data remains intact after the StorageMigration operation.
Cleanup
$ kubectl delete neo4jopsrequest -n demo storage-migration
$ kubectl delete neo4j -n demo neo4j-test
$ kubectl delete ns demo































