Backup and Restore Neo4j Composite Databases and Aliases
This guide shows you how to back up and restore physical databases, composite databases, composite aliases, and standalone aliases with KubeStash. It also explains the separate credential step required to restore stored-native remote aliases.
Before You Begin
- Prepare a Kubernetes cluster and configure
kubectlto access it. - Install KubeDB, KubeStash, and the KubeStash kubectl plugin.
- Use a KubeDB-supported Neo4j Enterprise version that supports composite databases. This guide uses
2025.12.1. - Prepare an S3-compatible bucket and its access credentials.
- Install a Java Development Kit that provides
keytoolon the machine where you run the setup commands. - Make sure the source and restore-target Neo4j deployments can reach the remote Neo4j Bolt endpoint.
- Read the Neo4j backup and restore overview if you are new to this process.
Create the namespace used throughout this guide:
$ kubectl create namespace demo
Note: Replace every placeholder in the example manifests before applying them.
Architecture and What Gets Backed Up
This walkthrough uses three distinct Neo4j deployments:
source-neo4jowns themoviesphysical database, themediacomposite database, and the alias catalog that KubeStash backs up.remote-neo4jowns thereviewsphysical database.source-neo4jreaches it through a remote alias. Its data is outside this backup’s scope.restored-neo4jreceives the physical database backup and reconstructed catalog.
KubeStash discovers the catalog through Neo4j’s system database. A backup contains physical database backup artifacts and metadata for composite database definitions, composite aliases, and standalone local and remote aliases. Depending on the alias, this metadata includes its location, target database, URL, username, credential type, driver settings, and properties.
The password for a stored-native remote alias is intentionally not stored in backup metadata. You must supply it separately during restore.
The database selector has the following behavior:
- The default
*selector backs up all visible physical databases and captures all composite and standalone alias definitions. - Selecting a composite database explicitly automatically includes physical databases referenced by its local aliases.
- A remote alias definition is captured, but the data in its remote target database is not backed up.
- A local alias target must be selected and restored successfully before its composite database and alias can be restored.
- Database and alias exclusions are honored. Do not exclude a physical database required by a selected local alias.
- The
systemdatabase is used for catalog discovery; it is not restored as a normal physical database.
During restore, KubeStash performs these operations in dependency order:
- Restore the selected physical databases.
- Wait for local alias target databases to become visible and online.
- Create or replace composite databases.
- Restore local and remote composite aliases.
- Restore standalone aliases.
- Verify the reconstructed composite and alias catalog.
Restore preflight checks catalog conflicts and required remote credentials before destructive restore work begins. Without overwrite, a conflicting physical database, composite database, or alias causes the restore to fail. With overwrite enabled, KubeStash replaces catalog definitions where supported. It may temporarily detach aliases that depend on a physical database being replaced and recreate them after that database is restored.
Deploy the Source and Remote Neo4j Instances
Generate the Remote Alias Encryption Key
Neo4j reversibly encrypts stored-native remote alias credentials in the system database. Before creating such an alias, generate a 256-bit AES key and store it in a password-protected PKCS12 keystore:
$ KEYSTORE_PASSWORD=$(openssl rand -base64 32)
$ keytool -genseckey -keyalg AES -keysize 256 -storetype PKCS12 \
-keystore neo4j-remote-alias-keystore.p12 -alias neo \
-storepass "$KEYSTORE_PASSWORD"
Create a Kubernetes Secret containing the keystore and its password. The Secret must exist before you create either Neo4j resource that references it:
$ kubectl create secret generic neo4j-remote-alias-keystore -n demo \
--from-file=aes=neo4j-remote-alias-keystore.p12 \
--from-literal=password="$KEYSTORE_PASSWORD"
The source-neo4j and restored-neo4j manifests reference the Secret through this configuration:
spec:
configuration:
remoteAliasKeystore:
keystoreRef:
name: neo4j-remote-alias-keystore
key: aes
passwordRef:
name: neo4j-remote-alias-keystore
key: password
keyName: neo
Here, aes is the Secret data key containing the PKCS12 file, password contains its password, and keyName must match the alias passed to keytool (neo in this example). Keep the keystore and password secure. Do not commit either one to source control.
Create the Neo4j Instances
Create the source instance:
apiVersion: kubedb.com/v1alpha2
kind: Neo4j
metadata:
name: source-neo4j
namespace: demo
spec:
version: 2025.12.1
replicas: 3
configuration:
remoteAliasKeystore:
keystoreRef:
name: neo4j-remote-alias-keystore
key: aes
passwordRef:
name: neo4j-remote-alias-keystore
key: password
keyName: neo
storageType: Durable
storage:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
deletionPolicy: WipeOut
Apply the manifest:
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.7.10/docs/guides/neo4j/backup/kubestash/logical/composite-database/examples/source-neo4j.yaml
Create the separate instance that will host the remote database:
apiVersion: kubedb.com/v1alpha2
kind: Neo4j
metadata:
name: remote-neo4j
namespace: demo
spec:
version: 2025.12.1
replicas: 3
storageType: Durable
storage:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
deletionPolicy: WipeOut
Apply the manifest:
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.7.10/docs/guides/neo4j/backup/kubestash/logical/composite-database/examples/remote-neo4j.yaml
Both manifests request three Neo4j replicas and durable 2Gi storage, and use WipeOut as the deletion policy. The source manifest also configures the remote alias keystore. Wait until both databases are ready:
$ kubectl wait --for=jsonpath='{.status.phase}'=Ready neo4j/source-neo4j neo4j/remote-neo4j -n demo --timeout=10m
Store the generated administrator passwords in shell variables. These commands do not print the values:
$ SOURCE_PASS=$(kubectl get secret source-neo4j-auth -n demo -o jsonpath='{.data.password}' | base64 -d)
$ REMOTE_PASS=$(kubectl get secret remote-neo4j-auth -n demo -o jsonpath='{.data.password}' | base64 -d)
Create Sample Physical Databases
Administrative commands run against the system database. Create movies on the source and reviews on the remote instance:
$ kubectl exec -n demo source-neo4j-0 -- cypher-shell -d system -u neo4j -p "$SOURCE_PASS" \
"CREATE DATABASE movies WAIT;"
$ kubectl exec -n demo remote-neo4j-0 -- cypher-shell -d system -u neo4j -p "$REMOTE_PASS" \
"CREATE DATABASE reviews WAIT;"
Add data to each physical database:
$ kubectl exec -n demo source-neo4j-0 -- cypher-shell -d movies -u neo4j -p "$SOURCE_PASS" \
"CREATE (:Movie {title: 'Example Movie'});"
$ kubectl exec -n demo remote-neo4j-0 -- cypher-shell -d reviews -u neo4j -p "$REMOTE_PASS" \
"CREATE (:Review {summary: 'Example Review'});"
Create a Composite Database
Create the media composite database on the source instance:
$ kubectl exec -n demo source-neo4j-0 -- cypher-shell -d system -u neo4j -p "$SOURCE_PASS" \
"CREATE COMPOSITE DATABASE media WAIT;"
Create a Local Alias
Create media.movies as a constituent local alias. Also create movies-local to demonstrate a standalone local alias:
$ kubectl exec -n demo source-neo4j-0 -- cypher-shell -d system -u neo4j -p "$SOURCE_PASS" \
"CREATE ALIAS media.movies FOR DATABASE movies;
CREATE ALIAS `movies-local` FOR DATABASE movies;"
The media prefix makes media.movies a constituent of the composite database. movies-local has no composite namespace and is therefore standalone.
Create a Stored-Native Remote Alias
With spec.configuration.remoteAliasKeystore configured, create media.reviews and a standalone alias named reviews-remote. The service URL addresses the separate remote-neo4j deployment. This in-cluster example uses the neo4j:// scheme, so its driver settings explicitly disable TLS enforcement. Use a secure neo4j+s:// URL in production.
$ kubectl exec -n demo source-neo4j-0 -- cypher-shell -d system -u neo4j -p "$SOURCE_PASS" \
"CREATE ALIAS media.reviews FOR DATABASE reviews
AT 'neo4j://remote-neo4j.demo.svc:7687'
USER neo4j PASSWORD '$REMOTE_PASS'
DRIVER {ssl_enforced: false}
PROPERTIES {purpose: 'reviews'};
CREATE ALIAS `reviews-remote` FOR DATABASE reviews
AT 'neo4j://remote-neo4j.demo.svc:7687'
USER neo4j PASSWORD '$REMOTE_PASS'
DRIVER {ssl_enforced: false};"
Do not place the real password in a manifest, documentation, shell history, or source control. The shell expands REMOTE_PASS only when this command runs.
Verify the Source Catalog
Use the system database for catalog queries:
$ kubectl exec -n demo source-neo4j-0 -- cypher-shell -d system -u neo4j -p "$SOURCE_PASS" \
"SHOW DATABASES
YIELD name, type, currentStatus
RETURN name, type, currentStatus
ORDER BY name;"
$ kubectl exec -n demo source-neo4j-0 -- cypher-shell -d system -u neo4j -p "$SOURCE_PASS" \
"SHOW ALIASES FOR DATABASE
YIELD name, composite, database, location, url, user
RETURN name, composite, database, location, url, user
ORDER BY name;"
Confirm that movies is standard and online, media is composite, both media.* aliases belong to media, and the two standalone aliases have a null composite value. The local aliases must target movies; the remote aliases must show location remote, database reviews, the configured URL, and user neo4j.
Verify that both composite constituents can be queried:
$ kubectl exec -n demo source-neo4j-0 -- cypher-shell -d media -u neo4j -p "$SOURCE_PASS" \
"USE media.movies MATCH (m:Movie) RETURN m.title;"
$ kubectl exec -n demo source-neo4j-0 -- cypher-shell -d media -u neo4j -p "$SOURCE_PASS" \
"USE media.reviews MATCH (r:Review) RETURN r.summary;"
Configure BackupStorage and RetentionPolicy
Create the S3 credential Secret as described in the basic logical backup guide. Then replace the placeholders in the following BackupStorage manifest:
apiVersion: storage.kubestash.com/v1alpha1
kind: BackupStorage
metadata:
name: s3-storage
namespace: demo
spec:
storage:
provider: s3
s3:
bucket: <your-bucket-name>
region: <your-bucket-region>
endpoint: <your-s3-endpoint>
secretName: s3-secret
prefix: demo
usagePolicy:
allowedNamespaces:
from: All
default: false
deletionPolicy: Delete
Apply the manifest after replacing the placeholders:
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.7.10/docs/guides/neo4j/backup/kubestash/logical/composite-database/examples/backupstorage.yaml
The provider block selects S3 and supplies its bucket, region, endpoint, credential Secret, and object prefix. usagePolicy permits repositories in all namespaces to use this storage. deletionPolicy: Delete removes stored backup data when the BackupStorage is deleted.
Create the retention policy:
apiVersion: storage.kubestash.com/v1alpha1
kind: RetentionPolicy
metadata:
name: demo-retention
namespace: demo
spec:
default: true
failedSnapshots:
last: 2
maxRetentionPeriod: 2mo
successfulSnapshots:
last: 5
usagePolicy:
allowedNamespaces:
from: All
Apply the manifest:
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.7.10/docs/guides/neo4j/backup/kubestash/logical/composite-database/examples/retentionpolicy.yaml
This policy retains the last five successful and two failed snapshots for at most two months. Its usage policy allows all namespaces.
Create BackupConfiguration
Apply the backup configuration:
apiVersion: core.kubestash.com/v1alpha1
kind: BackupConfiguration
metadata:
name: source-neo4j-backup
namespace: demo
spec:
target:
apiGroup: kubedb.com
kind: Neo4j
namespace: demo
name: source-neo4j
backends:
- name: s3-backend
storageRef:
namespace: demo
name: s3-storage
retentionPolicy:
name: demo-retention
namespace: demo
sessions:
- name: frequent-backup
scheduler:
schedule: "*/5 * * * *"
jobTemplate:
backoffLimit: 1
repositories:
- name: s3-neo4j-composite-repo
backend: s3-backend
directory: /neo4j-composite
addon:
name: neo4j-addon
tasks:
- name: logical-backup
Apply the manifest:
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.7.10/docs/guides/neo4j/backup/kubestash/logical/composite-database/examples/backupconfiguration.yaml
The target is source-neo4j. The backend refers to s3-storage and demo-retention. The session runs every five minutes and writes to repository s3-neo4j-composite-repo under /neo4j-composite. The logical-backup task uses its default * selector, so it backs up all visible physical databases and captures every composite and standalone alias definition.
To select only media, set databases: "media" in the task parameters. KubeStash will also select movies because media.movies depends on it. Do not exclude movies from that backup.
Verify the Backup and Snapshot
Wait for the configuration and its first backup to succeed:
$ kubectl get backupconfiguration -n demo source-neo4j-backup
$ kubectl get backupsession -n demo -w
Then inspect the repository and snapshot:
$ kubectl get repository -n demo s3-neo4j-composite-repo
$ kubectl get snapshot -n demo -l kubestash.com/repo-name=s3-neo4j-composite-repo
The BackupConfiguration and repository should become Ready, and the BackupSession and snapshot should reach Succeeded. Snapshot names and timings are generated, so this guide does not show fabricated output. Save the snapshot name if you prefer to restore a fixed snapshot instead of latest.
Deploy the Restore Target
Create the empty restore target:
apiVersion: kubedb.com/v1alpha2
kind: Neo4j
metadata:
name: restored-neo4j
namespace: demo
spec:
version: 2025.12.1
replicas: 3
configuration:
remoteAliasKeystore:
keystoreRef:
name: neo4j-remote-alias-keystore
key: aes
passwordRef:
name: neo4j-remote-alias-keystore
key: password
keyName: neo
storageType: Durable
storage:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
deletionPolicy: WipeOut
Apply the manifest:
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.7.10/docs/guides/neo4j/backup/kubestash/logical/composite-database/examples/restored-neo4j.yaml
The target uses the same Neo4j version, topology, and remote alias keystore configuration. The restore Job will seed restored-neo4j-0 through its PVC. The keystore configuration lets the restored Neo4j instance encrypt credentials while KubeStash recreates stored-native remote aliases.
Create the Remote Alias Credential Secret
Stored-native alias passwords are never included in backup metadata. Create this Secret in the same namespace as the RestoreSession before starting the restore:
apiVersion: v1
kind: Secret
metadata:
name: neo4j-remote-alias-credentials
namespace: demo
type: Opaque
stringData:
credentials.yaml: |
media.reviews:
password: "<remote-database-password>"
reviews-remote:
password: "<remote-database-password>"
Save this manifest as remote-alias-credentials.yaml, replace the placeholders locally, and apply it. Do not commit real values:
$ kubectl apply -f remote-alias-credentials.yaml
The Secret must contain a credentials.yaml key. Its value is a map keyed by the exact, complete alias name returned by SHOW ALIASES FOR DATABASE. Every selected stored-native remote alias needs a non-empty password. The username, URL, driver settings, and properties come from backup metadata; only the password comes from this Secret. The remote endpoint must be reachable from restored-neo4j. OIDC credential-forwarding aliases do not use stored passwords and do not need entries.
Create RestoreSession
Create the restore session after the credential Secret exists:
apiVersion: core.kubestash.com/v1alpha1
kind: RestoreSession
metadata:
name: source-neo4j-restore
namespace: demo
spec:
target:
apiGroup: kubedb.com
kind: Neo4j
namespace: demo
name: restored-neo4j
dataSource:
repository: s3-neo4j-composite-repo
snapshot: latest
addon:
name: neo4j-addon
tasks:
- name: logical-backup-restore
params:
seedServerName: "restored-neo4j-0"
remoteAliasCredentialsSecret: neo4j-remote-alias-credentials
jobTemplate:
spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: data-restored-neo4j-0
volumeMounts:
- mountPath: /data
name: data
subPath: data
securityContext:
runAsNonRoot: true
runAsUser: 7474
Apply the manifest:
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.7.10/docs/guides/neo4j/backup/kubestash/logical/composite-database/examples/restoresession.yaml
The target is restored-neo4j, and the data source selects the latest snapshot from s3-neo4j-composite-repo. The logical-backup-restore task uses restored-neo4j-0 as the seed server. remoteAliasCredentialsSecret identifies the Secret that supplies stored-native remote alias passwords. The restore Job mounts data-restored-neo4j-0 at /data with subpath data and runs as the Neo4j user 7474, ensuring correct ownership of restored files.
The addon’s default restore arguments enable overwrite. If overwrite is disabled, any conflicting physical database, composite database, or alias fails preflight. Review the target catalog before restoring into a non-empty deployment.
Watch the restore:
$ kubectl get restoresession -n demo source-neo4j-restore -w
Continue only after its phase is Succeeded and restored-neo4j is Ready.
Verify the Restored Physical Databases, Composite Database, and Aliases
Load the restore-target password without printing it:
$ RESTORED_PASS=$(kubectl get secret restored-neo4j-auth -n demo -o jsonpath='{.data.password}' | base64 -d)
Verify database type and status:
$ kubectl exec -n demo restored-neo4j-0 -- cypher-shell -d system -u neo4j -p "$RESTORED_PASS" \
"SHOW DATABASES
YIELD name, type, currentStatus
RETURN name, type, currentStatus
ORDER BY name;"
Confirm that movies is online and that media has type composite. Next, compare alias metadata with the source catalog:
$ kubectl exec -n demo restored-neo4j-0 -- cypher-shell -d system -u neo4j -p "$RESTORED_PASS" \
"SHOW ALIASES FOR DATABASE
YIELD name, composite, database, location, url, user
RETURN name, composite, database, location, url, user
ORDER BY name;"
Confirm the expected composite membership, target, location, URL, and username for all four aliases. Finally, query both composite constituents:
$ kubectl exec -n demo restored-neo4j-0 -- cypher-shell -d media -u neo4j -p "$RESTORED_PASS" \
"USE media.movies MATCH (m:Movie) RETURN m.title;"
$ kubectl exec -n demo restored-neo4j-0 -- cypher-shell -d media -u neo4j -p "$RESTORED_PASS" \
"USE media.reviews MATCH (r:Review) RETURN r.summary;"
$ kubectl get restoresession -n demo source-neo4j-restore
The local query reads restored movies data. The remote query reads the live reviews database on remote-neo4j; it does not prove that remote data was part of the backup. The final command must report the RestoreSession phase as Succeeded.
Cleanup
Delete the tutorial resources when you no longer need them:
$ kubectl delete restoresession -n demo source-neo4j-restore
$ kubectl delete backupconfiguration -n demo source-neo4j-backup
$ kubectl delete backupstorage -n demo s3-storage
$ kubectl delete retentionpolicy -n demo demo-retention
$ kubectl delete secret -n demo neo4j-remote-alias-credentials neo4j-remote-alias-keystore s3-secret
$ kubectl delete neo4j -n demo restored-neo4j source-neo4j remote-neo4j
Because these Neo4j resources use deletionPolicy: WipeOut, deleting them also removes their database storage. The BackupStorage uses deletionPolicy: Delete, so deleting it removes its stored backup data.
































