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 kubectl configured.
  • Install KubeDB operator following setup guide.
  • Ensure at least two StorageClass resources 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 with WaitForFirstConsumer as 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 User nodes
  • 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.type must be StorageMigration.
  • spec.databaseRef.name points to target Neo4j database.
  • spec.migration.storageClassName is the destination StorageClass.
  • spec.migration.oldPVReclaimPolicy controls 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