Neo4jEnterpriseCluster API Reference¶
The Neo4jEnterpriseCluster Custom Resource Definition (CRD) manages Neo4j Enterprise clusters with high availability, automatic failover, and horizontal scaling capabilities.
Overview¶
- API Version:
neo4j.neo4j.com/v1beta1 - Kind:
Neo4jEnterpriseCluster - Supported Neo4j Versions: 5.26.x (last semver LTS) and 2025.01.0+ (CalVer)
- Architecture: Server-based deployment with unified StatefulSet
- Minimum Servers: 2 (required for clustering)
- Maximum Servers: 100 (validated limit)
- Name Limit:
metadata.nameis at most 56 characters (generated resources append suffixes; validated)
Architecture¶
Server-Based Architecture: Neo4jEnterpriseCluster uses a unified server-based architecture:
- Single StatefulSet:
{cluster-name}-serverwith configurable replica count - Server Pods: Named
{cluster-name}-server-0,{cluster-name}-server-1, etc. - Self-Organization: Servers automatically organize into primary/secondary roles for databases
- Backup: Use the
Neo4jBackupCRD — each CR spawns a Kubernetes Job. There is no persistent backup pod or sidecar. - Role Flexibility: Servers can host multiple databases with different roles
When to Use:
- Production workloads requiring high availability
- Multi-database deployments with different topology requirements
- Workloads needing horizontal read scaling
- Enterprise features like advanced backup, security, and monitoring
- Large datasets requiring property sharding (Neo4j 2025.12+)
For single-node deployments, use Neo4jEnterpriseStandalone instead.
Related Resources¶
Neo4jDatabase- Create databases within the clusterNeo4jShardedDatabase- Create sharded databases for horizontal scalingNeo4jPlugin- Install plugins (APOC, GDS, etc.)Neo4jBackup- Schedule automated backupsNeo4jRestore- Restore from backups
Spec¶
The Neo4jEnterpriseClusterSpec defines the desired state of a Neo4j Enterprise cluster.
Core Configuration¶
| Field | Type | Required | Description |
|---|---|---|---|
image |
ImageSpec |
Required | The Neo4j Docker image configuration |
topology |
TopologyConfiguration |
Required | Cluster topology (number of servers) |
storage |
StorageSpec |
Required | Storage configuration for data persistence |
acceptLicenseAgreement |
string (yes | eval) |
Required | Your explicit acceptance of the Neo4j Enterprise license. yes accepts the commercial license agreement; eval accepts the 30-day evaluation license. This operator is independent and does not accept the license on your behalf — you must hold your own valid Neo4j Enterprise license. The operator refuses to deploy until this is set. |
auth |
AuthSpec |
Optional | Authentication configuration |
Kubernetes Integration¶
| Field | Type | Description |
|---|---|---|
resources |
*corev1.ResourceRequirements |
CPU and memory resources |
env |
[]corev1.EnvVar |
Environment variables for Neo4j pods |
nodeSelector |
map[string]string |
Node selector constraints |
tolerations |
[]corev1.Toleration |
Pod tolerations |
affinity |
*corev1.Affinity |
Pod affinity rules |
securityContext |
*SecurityContextSpec |
Pod/container security overrides |
Neo4j Configuration¶
| Field | Type | Description |
|---|---|---|
config |
map[string]string |
Custom Neo4j configuration |
Operations¶
| Field | Type | Description |
|---|---|---|
upgradeStrategy |
UpgradeStrategySpec |
Upgrade strategy configuration |
To back up a cluster, create a separate Neo4jBackup CR. To
restore a cluster from a backup, create a separate Neo4jRestore CR after the
cluster reaches Ready — see Neo4jRestore.
Networking¶
| Field | Type | Description |
|---|---|---|
service |
ServiceSpec |
Service configuration for external access |
Extensions¶
| Field | Type | Description |
|---|---|---|
tls |
TLSSpec |
TLS configuration |
mcp |
MCPServerSpec |
MCP server deployment and exposure settings |
propertySharding |
PropertyShardingSpec |
Property sharding configuration (Neo4j 2025.12+) |
monitoring |
MonitoringSpec |
Query monitoring configuration |
audit |
AuditSpec |
Compliance-oriented logging — security.log + query.log tuning for PCI / HIPAA / GDPR. audit.enabled=true is a one-flag opt-in to secure-by-default query-literal redaction. |
networkPolicy |
NetworkPolicySpec |
Optional NetworkPolicy emission that restricts ingress to port 6362 to operator-managed backup pods. Disabled by default. Requires a CNI that enforces NetworkPolicy (Calico/Cilium/Antrea/Weave). |
crossClusterReplication |
CrossClusterReplicationSpec |
Network-mode cross-cluster database replication (CCDR): deploys a self-hosted TCP proxy exposing this cluster's tx-shipping port (6000) externally via one LoadBalancer Service, for use by a Neo4jReplicaDatabase with source.mode: network in another Kubernetes cluster. Disabled by default. Requires tls.mode: cert-manager — the proxy is a TCP passthrough that authenticates nothing, so the cluster SSL policy is the only access control on the exposed port; enabling it without TLS is rejected. |
auraFleetManagement |
AuraFleetManagementSpec |
Aura Fleet Management integration (optional) |
trustedCASecrets |
[]TrustedCASecret |
CA bundles to add to Neo4j's JVM truststore (OIDC, LDAPS, plugin downloads, peer-cluster replication) |
serverDefaultCypherLanguage |
string |
Cypher version a database gets when it is created without one of its own — Neo4j's db.query.default_language. CYPHER_5 or CYPHER_25. Unset, the operator picks once and records it in status.effectiveCypherLanguage: CYPHER_25 for a deployment it creates on a CalVer image, CYPHER_5 on the 5.26 LTS, and whatever an existing deployment already runs — an operator upgrade never changes it. Neo4j fixes a database's language at creation, so this applies only to databases created afterwards; change an existing one with ALTER DATABASE … SET DEFAULT LANGUAGE. Changing the field restarts the servers. CYPHER_25 needs a CalVer image, and nothing is ever written on the LTS (the setting does not exist there). Don't also set db.query.default_language in spec.config; the two are refused when they disagree. |
remoteAliasKeystore |
RemoteAliasKeystoreSpec |
Keystore used to encrypt the credentials of remote database aliases that store native credentials — including the remote constituents of a Neo4jCompositeDatabase. Required for that mode only; aliases using OIDC credential forwarding store no credential and need no keystore. |
extraVolumes |
[]corev1.Volume |
Arbitrary pod volumes mounted into the Neo4j pod; reference them via extraVolumeMounts |
extraVolumeMounts |
[]corev1.VolumeMount |
Mount points for extraVolumes (or, rarely, operator-managed volumes); operator-managed paths are rejected by the validator |
extraEnvFrom |
[]corev1.EnvFromSource |
Standard Kubernetes pass-through. Projects entire Secrets or ConfigMaps as environment variables onto the Neo4j container. Intended for credential bundles (e.g. cloud seed credentials for seedURI-based restores). |
podServiceAccountAnnotations |
map[string]string |
Annotations stamped onto the operator-managed ServiceAccount the Neo4j server pods run under. Use for cloud Workload Identity so the JVM can assume a cloud role when it fetches a seed URI (cluster restore, Neo4jDatabase/Neo4jShardedDatabase seeding) — e.g. eks.amazonaws.com/role-arn (AWS IRSA), iam.gke.io/gcp-service-account (GKE WI), or the Azure WI labels. Merged additively (operator owns only the keys you list; out-of-band annotations preserved). Set this before the pods start (or roll the StatefulSet after adding it) so the identity webhook injects the token at pod admission. |
Type Definitions¶
ImageSpec¶
| Field | Type | Description |
|---|---|---|
repo |
string |
Required. Image repository (e.g. "neo4j") |
tag |
string |
Required. Image tag |
pullPolicy |
string |
Pull policy: "Always", "IfNotPresent", "Never" (default: "IfNotPresent") |
pullSecrets |
[]string |
Image pull secrets |
TopologyConfiguration¶
Defines the cluster server topology and role constraints.
| Field | Type | Description |
|---|---|---|
servers |
int32 |
Required. Number of Neo4j servers (minimum: 2, maximum: 100) |
minSystemPrimaries |
*int32 |
System-database primary count written to dbms.cluster.minimum_initial_system_primaries_count at cluster formation, and the floor the cluster can later be scaled down to. Default: min(3, servers). Validator: must be ≥ 2 and ≤ servers; an even value draws a warning (an even voting set has no clean write-quorum majority — prefer 3, 5, …). It also sets each server's role for the system database: servers with an ordinal below it start as system primaries, the rest as system secondaries (fixed at a server's first start and recorded on its volume; servers created before this behaviour keep their role). Adding servers past it does not restart the running servers. See System primaries and secondaries |
serverModeConstraint |
string |
Global server mode constraint: "NONE" (default), "PRIMARY", "SECONDARY" |
serverRoles |
[]ServerRoleHint |
Per-server role constraints (overrides global constraint) |
placement |
*PlacementConfig |
Advanced placement and scheduling configuration |
availabilityZones |
[]string |
Target availability zones for server distribution |
enforceDistribution |
bool |
Enforce server distribution across topology domains |
Server Role Management:
- Servers self-organize into primary/secondary roles at the database level
- Role constraints influence which databases a server can host:
NONE: Server can host databases in any mode (default)PRIMARY: Server only hosts databases in primary modeSECONDARY: Server only hosts databases in secondary mode- Use
serverRolesfor granular per-server control
Validation:
- Minimum 2 servers required for clustering
- Cannot configure all servers as
SECONDARY(cluster needs primaries) - Server indices in
serverRolesmust be within range (0 to servers-1)
ServerRoleHint¶
Specifies role constraints for individual servers.
| Field | Type | Description |
|---|---|---|
serverIndex |
int32 |
Required. Server index (0-based, must be < servers count) |
modeConstraint |
string |
Required. Role constraint: "NONE", "PRIMARY", "SECONDARY" |
StorageSpec¶
| Field | Type | Description |
|---|---|---|
className |
string |
Storage class for the data PVCs. Optional — if omitted, the PVCs inherit the cluster's default StorageClass. When set, the named class must exist: the operator reports an explicit error (a StorageClassNotFound event and Failed status) rather than leaving server pods Pending. Immutable after creation. |
size |
string |
Required. Storage size (e.g., "10Gi"). Must be a valid Kubernetes quantity greater than zero, the same rule as a standalone: "1.5Gi" is fine; "0", "fifty", "10 Gi" and a capital K suffix ("5K", not a Kubernetes suffix — use Ki, or lowercase k) are refused by validation. Can be increased after creation — the operator automatically expands PVCs and recreates the StatefulSet with zero downtime. Cannot be decreased (PVC shrink is not supported by Kubernetes). Requires the StorageClass to have allowVolumeExpansion: true. |
retentionPolicy |
string |
PVC retention policy: "Delete" (default) permanently removes PVCs on deletion; "Retain" preserves them. Use Retain for production to prevent data loss. See Storage and PVC Retention. |
AuthSpec¶
| Field | Type | Description |
|---|---|---|
authenticationProviders |
[]string |
Ordered list of authentication providers (e.g., ["ldap", "native"]), written into dbms.security.authentication_providers. Values: native, ldap, oidc-<name>, plugin-<name>; the older oidc, kerberos, jwt, saml, custom validate with a warning. Default: ["native"] |
authorizationProviders |
[]string |
Ordered list of authorization providers, written into dbms.security.authorization_providers. Same values. Default: ["native"] |
adminSecret |
string |
Secret containing admin username and password (keys: username, password) |
externalSecrets |
*ExternalSecretsConfig |
External secrets configuration |
ldap |
*Neo4jLDAPSpec |
LDAP authentication and authorization configuration |
oidc |
map[string]Neo4jOIDCProviderSpec |
OIDC/SSO providers. Map key = provider name (used in authenticationProviders as oidc-<key>). For JWT-based auth, use OIDC — Neo4j ID tokens are JWTs. |
authCacheTTL |
string |
Auth cache TTL (e.g., "10m", "600s"). Maps to dbms.security.auth_cache_ttl |
trustStore |
*SecretKeyRef |
Custom JVM truststore for LDAPS or OIDC with internal CAs |
Neo4jLDAPSpec¶
| Field | Type | Description |
|---|---|---|
host |
string |
Required. LDAP server URL (e.g., ldap://host:389 or ldaps://host:636) |
useStartTLS |
*bool |
Enable STARTTLS (use with ldap://, not ldaps://). Defaults to true when host starts with ldap:// and the field is unset — the Neo4j security checklist's secure-by-default. Set to false explicitly to opt out (dev / mock-LDAP setups). |
authentication |
*LDAPAuthenticationSpec |
User authentication settings |
authorization |
*LDAPAuthorizationSpec |
Group/role authorization settings |
debugGroupLogging |
*bool |
Debug-level LDAP group logging (disable in production) |
LDAPAuthenticationSpec¶
| Field | Type | Description |
|---|---|---|
userDNTemplate |
string |
DN template for binding. {0} = username. Example: "{0}@example.com" |
searchForAttribute |
*bool |
Use attribute-based user lookup instead of DN template |
attribute |
string |
LDAP attribute to search (e.g., "samaccountname") |
cacheEnabled |
*bool |
Cache authentication results |
LDAPAuthorizationSpec¶
| Field | Type | Description |
|---|---|---|
userSearchBase |
string |
Base DN for user search (e.g., "dc=example,dc=com") |
userSearchFilter |
string |
LDAP filter for user search. {0} = username |
groupMembershipAttributes |
[]string |
User attributes containing group membership (e.g., ["memberOf"]) |
groupToRoleMapping |
map[string]string |
Map of LDAP group DNs to comma-separated Neo4j roles |
accessPermittedGroup |
string |
Restrict access to members of this group |
useSystemAccount |
*bool |
Use a system account for authorization queries |
systemAccountSecretRef |
string |
Secret name with username and password keys for the system account |
nestedGroupsEnabled |
*bool |
Enable recursive group resolution |
nestedGroupsSearchFilter |
string |
Filter for nested group lookups. {0} = user DN |
Neo4jOIDCProviderSpec¶
⚠️ All URI fields require
https://— Neo4j 2026.x rejectshttp://forwellKnownDiscoveryURI,authEndpoint,tokenEndpoint,jwksURI,userInfoURI, andissuerat config-parse time, and the cluster fails to start. There is no insecure-mode override. Self-hosted IDPs without TLS need a TLS-terminating proxy plus the proxy's CA inspec.trustedCASecrets.
| Field | Type | Description |
|---|---|---|
displayName |
string |
Display name on login screen |
wellKnownDiscoveryURI |
string |
OIDC discovery endpoint (auto-configures other endpoints). HTTPS only. |
authEndpoint |
string |
Authorization endpoint (manual override). HTTPS only. |
tokenEndpoint |
string |
Token endpoint (manual override). HTTPS only. |
jwksURI |
string |
JWKS endpoint (manual override). HTTPS only. |
userInfoURI |
string |
UserInfo endpoint (manual override). HTTPS only. |
issuer |
string |
Issuer identifier (manual override). HTTPS only. |
audience |
string |
Required. Expected JWT aud claim |
authFlow |
string |
"pkce" (default) or "implicit" |
claims |
*OIDCClaimsSpec |
JWT claim mapping |
getGroupsFromUserInfo |
*bool |
Fetch groups from UserInfo endpoint |
getUsernameFromUserInfo |
*bool |
Fetch username from UserInfo endpoint |
groupToRoleMapping |
map[string]string |
Map of IdP groups to comma-separated Neo4j roles |
authParams |
string |
Extra authorization endpoint params (semicolon-separated) |
tokenParams |
string |
Extra token endpoint params (semicolon-separated) |
OIDCClaimsSpec¶
| Field | Type | Description |
|---|---|---|
username |
string |
JWT claim for Neo4j username (default: "sub") |
groups |
string |
JWT claim for groups/roles |
SecretKeyRef (TrustStore)¶
Used by the legacy singular spec.auth.trustStore field. New configurations
should prefer the plural TrustedCASecret list at
spec.trustedCASecrets; both fields are merged at reconcile time.
| Field | Type | Description |
|---|---|---|
name |
string |
Required. Name of Secret containing CA certificate (PEM format) |
key |
string |
Key in the Secret containing the CA cert (default: "ca.crt") |
RemoteAliasKeystoreSpec¶
Neo4j encrypts the credentials of remote database aliases before storing them in the system database, and refuses to create such an alias without a keystore:
Failed to create alias for remote database: the required setting(s)
[dbms.security.keystore.path, dbms.security.keystore.password] are missing
| Field | Type | Description |
|---|---|---|
secretRef |
string |
Required. A Secret in this namespace with two keys: keystore.p12 (the PKCS12 keystore) and password. The password reaches Neo4j as an environment variable sourced from this Secret, so it never appears in the operator-managed ConfigMap, and SHOW SETTINGS redacts it server-side. |
keyName |
string |
Required. The keytool alias of the secret key inside the keystore — the -alias given to keytool -genseckey. Maps to dbms.security.key.name. |
The operator does not generate the keystore. Create it with the JDK's keytool — ideally on the same Java version Neo4j runs, as Neo4j's documentation advises:
keytool -genseckey -keyalg aes -keysize 256 -storetype pkcs12 \
-keystore keystore.p12 -alias my-key -storepass "$PASSWORD"
kubectl create secret generic remote-alias-keystore \
--from-file=keystore.p12 --from-literal=password="$PASSWORD"
Every server mounts the same Secret, which satisfies Neo4j's requirement that the keystore file be identical across a cluster. Generating one per pod would give each server a different key and leave every alias readable by exactly one of them.
Rotating the keystore invalidates every existing remote alias
Neo4j encrypts each alias's credentials with this key. Changing the keystore or the key name makes all existing remote-alias credentials permanently unreadable and the aliases must be recreated, so rotation is a delete-and-recreate operation rather than an edit.
TrustedCASecret¶
References a Secret containing a PEM-encoded CA certificate to add to Neo4j's
JVM truststore. Used by spec.trustedCASecrets.
| Field | Type | Description |
|---|---|---|
name |
string |
Required. Name of the Secret. Doubles as the keytool alias inside the JKS, so names must be unique across the list. |
key |
string |
Key within the Secret. Defaults to "ca.crt" — the layout cert-manager-issued Secrets use. |
The operator merges every entry into a JKS truststore at
/truststore/truststore.jks (password changeit) using a truststore-init
init container, then sets -Djavax.net.ssl.trustStore on the JVM. The JDK's
default cacerts is used as the seed, so adding internal CAs does not
remove trust in public CAs.
Example with cert-manager:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata: { name: oidc-tls }
spec:
secretName: oidc-tls # ← name to reference below
issuerRef: { name: corp-ca, kind: ClusterIssuer }
commonName: idp.corp.example.com
dnsNames: [idp.corp.example.com]
---
apiVersion: neo4j.neo4j.com/v1beta1
kind: Neo4jEnterpriseCluster
spec:
acceptLicenseAgreement: "eval"
trustedCASecrets:
- name: oidc-tls # default key "ca.crt" matches cert-manager
For the less common case where a Neo4j SSL policy needs a CA at a specific
filesystem path (e.g. dbms.ssl.policy.<name>.truststore_path), use
spec.extraVolumes + spec.extraVolumeMounts instead — the operator does
not parse policy config to wire up filesystem mounts automatically.
SecurityContextSpec¶
| Field | Type | Description |
|---|---|---|
podSecurityContext |
*corev1.PodSecurityContext |
Pod-level security settings |
containerSecurityContext |
*corev1.SecurityContext |
Container-level security settings |
TLSSpec¶
| Field | Type | Description |
|---|---|---|
mode |
string |
TLS mode: "cert-manager" (default) or "disabled". The validator rejects any other value. |
issuerRef |
*IssuerRef |
cert-manager issuer reference. Required when mode: cert-manager. Validator rejects a cert-manager mode with no issuerRef. |
certificateSecret |
string |
Reserved — no effect today. Intended for manual certificates, but no builder or controller reads it: the operator always uses the cert-manager-issued Secret {name}-tls-secret. A ValidationWarning event is raised whenever it is set, whatever mode is. |
trustedCASecret |
string |
Secret containing a trusted CA certificate (key: ca.crt) for verifying Neo4j TLS connections. When omitted, the operator auto-discovers the CA from the cert-manager-generated Secret. |
externalSecrets |
*ExternalSecretsConfig |
External Secrets configuration |
duration |
*string |
Certificate duration (e.g., "2160h") |
renewBefore |
*string |
Renewal window before expiry (e.g., "360h") |
subject |
*CertificateSubject |
Certificate subject fields |
usages |
[]string |
Certificate usages (max 32 entries, each from the cert-manager key-usage list, e.g. digital signature, key encipherment, server auth, client auth). When set it replaces the operator defaults and must include both server auth and client auth (CEL rule and validator) — Neo4j needs both EKUs for its mutual-TLS posture. Omit to use the defaults. |
strictPeerValidation |
*bool |
Intra-cluster TLS posture. Default: true (Neo4j's canonical production posture). When true, the operator emits dbms.ssl.policy.cluster.trust_all=false, client_auth=REQUIRE (mutual TLS), and verify_hostname=true, and projects the cert-manager Secret's ca.crt to /ssl/trusted/ca.crt as the trust anchor. When false, the operator emits trust_all=true + client_auth=NONE instead — Neo4j's own docs flag this as "debugging only, since it does not offer security." The opt-out exists for external issuers that do not populate ca.crt in their Secret output. See the TLS Configuration Guide for details. |
additionalClusterTrustCAs |
[]TrustedCASecret |
Peer clusters' CA certificates, each projected as its own file directly into /ssl/trusted/ (the cluster SSL policy's trust directory — Neo4j scans every file present there, not one fixed filename) alongside this cluster's own CA. Required for mutual TLS when replicating across clusters with different CAs — the normal case for crossClusterReplication — since trustedCASecrets above is a JVM-wide truststore that dbms.ssl.policy.cluster.* never reads. Must be set on both clusters, each trusting the other's CA. This list is the access-control list for the exposed port, so removing a decommissioned peer's CA is a revocation step, not housekeeping. Read only when mode is cert-manager — setting it without the mode is rejected rather than silently ignored. Only relevant when strictPeerValidation is true. |
IssuerRef¶
| Field | Type | Description |
|---|---|---|
name |
string |
Required. Issuer resource name |
kind |
string |
Issuer resource kind. Default: "ClusterIssuer". Use "Issuer" for namespace-scoped issuers, or any external issuer kind (e.g. "AWSPCAClusterIssuer", "VaultIssuer", "GoogleCASClusterIssuer") |
group |
string |
API group of the issuer. Default: cert-manager.io. Set to the external issuer's API group when using third-party issuers (e.g. "awspca.cert-manager.io") |
Examples:
# Standard cert-manager ClusterIssuer (default)
issuerRef:
name: ca-cluster-issuer
kind: ClusterIssuer
# Namespace-scoped Issuer
issuerRef:
name: my-issuer
kind: Issuer
# AWS Private CA (external issuer)
issuerRef:
name: aws-pca-issuer
kind: AWSPCAClusterIssuer
group: awspca.cert-manager.io
# HashiCorp Vault
issuerRef:
name: vault-issuer
kind: VaultIssuer
group: cert.cert-manager.io
CertificateSubject¶
| Field | Type | Description |
|---|---|---|
organizations |
[]string |
Organization names |
countries |
[]string |
Country codes |
organizationalUnits |
[]string |
Organizational units |
localities |
[]string |
Localities |
provinces |
[]string |
Provinces/States |
ExternalSecretsConfig¶
Requires External Secrets Operator to be installed separately —
the operator only emits an ExternalSecret; ESO resolves it. A ClusterSecretStore works in
both RBAC modes, because ESO (not this operator) performs the cluster-scoped read — see
Operator modes.
Minimum External Secrets Operator version: v0.17.0
The operator emits apiVersion: external-secrets.io/v1. If you are on ESO < 0.17.0,
upgrade ESO (its own migration guide
covers the path).
| Field | Type | Description |
|---|---|---|
enabled |
bool |
Enable External Secrets integration |
secretStoreRef |
*SecretStoreRef |
SecretStore or ClusterSecretStore reference |
refreshInterval |
string |
Refresh interval (e.g., "1h"). Defaults to 1h when unset |
data |
[]ExternalSecretData |
External secret data mappings |
SecretStoreRef¶
| Field | Type | Description |
|---|---|---|
name |
string |
SecretStore name |
kind |
string |
"SecretStore" or "ClusterSecretStore" |
ExternalSecretData¶
| Field | Type | Description |
|---|---|---|
secretKey |
string |
Target secret key |
remoteRef |
*ExternalSecretRemoteRef |
Remote secret reference |
ExternalSecretRemoteRef¶
| Field | Type | Description |
|---|---|---|
key |
string |
External secret key |
property |
string |
Property within the secret |
version |
string |
Secret version |
AuraFleetManagementSpec¶
Enables integration with Neo4j Aura Fleet Management for monitoring this cluster from the Aura console. The fleet-management plugin is pre-bundled in all Neo4j Enterprise images (no internet access required).
| Field | Type | Description |
|---|---|---|
enabled |
bool |
Enable Aura Fleet Management integration (default: false) |
tokenSecretRef |
*SecretKeyRef |
Reference to the Kubernetes Secret holding the Aura registration token (optional; registration deferred if omitted). Mutually exclusive with provision (enforced by a CEL rule: set at most one of provision or tokenSecretRef). |
provision |
*AuraFleetProvisionSpec |
Let the operator register the deployment with Aura Fleet Manager and mint the registration token itself via the Aura API, instead of the console-wizard step. Beta / best-effort (the Fleet Manager request bodies are not published in the Aura OpenAPI spec). Mutually exclusive with tokenSecretRef. |
AuraFleetProvisionSpec¶
Exactly one of providerConfigRef or credentialsSecretRef must be set (CEL rule: set exactly one of providerConfigRef or credentialsSecretRef).
| Field | Type | Description |
|---|---|---|
providerConfigRef |
corev1.LocalObjectReference |
AuraProviderConfig (credentials plus default organization/project) in the same namespace. Mutually exclusive with credentialsSecretRef. |
credentialsSecretRef |
AuraCredentialsSecretRef |
Single-account shortcut when no AuraProviderConfig is used: name (required), clientIdKey (default clientId), clientSecretKey (default clientSecret). Mutually exclusive with providerConfigRef. |
organizationId |
string |
Aura organization owning the fleet deployment; defaults to the AuraProviderConfig's defaultOrganizationId. |
projectId |
string |
Aura project (API tenant_id); defaults to the AuraProviderConfig's defaultProjectId. |
deploymentName |
string |
Name registered in Fleet Manager, max 30 characters. Default <namespace>-<name> truncated to 30 characters. |
tokenSecretName |
string |
Secret the minted token is written to (key token), created in the CR's namespace and owned by the CR. Default <name>-aura-fleet-token. |
tokenPolicy |
string |
CreateIfMissing (default) mints a token only when the Aura deployment has none — a token already registered successfully is never rotated, and the operator reports the problem instead. Rotate mints a fresh token whenever the Secret is missing or empty, invalidating any existing registration (the DBMS must re-register). |
deletionPolicy |
string |
Aura-side behaviour on CR deletion: Orphan (default; leave it registered) or Delete (revoke the token and unregister the deployment). |
managementPolicies |
[]string |
Restricts which actions the operator may take against the Fleet Manager API; items are Observe, Create, Delete or * (default ["*"]). |
collectTelemetry |
bool |
Mirror Aura's own view of the deployment's servers and databases into status.auraFleetManagement (default false). Kept separate from status.diagnostics, which is the operator's Bolt-derived view; telemetry failures never fail a reconcile. |
Status fields (read-only, set by the operator):
| Field | Description |
|---|---|
status.auraFleetManagement.registered |
true once fleetManagement.registerToken succeeded |
status.auraFleetManagement.lastRegistrationTime |
Timestamp of last successful registration |
status.auraFleetManagement.message |
Human-readable status or error detail |
status.auraFleetManagement.provisioned |
true once the operator has registered a Fleet Manager deployment and stored a token for it (only meaningful with spec.provision) |
status.auraFleetManagement.deploymentId |
Aura-assigned Fleet Manager deployment ID (also mirrored to the neo4j.com/external-fleet-deployment-id annotation, the authoritative idempotency guard) |
status.auraFleetManagement.deploymentName |
Name the deployment is registered under in Aura |
status.auraFleetManagement.tokenSecretName |
Secret the minted token was written to |
status.auraFleetManagement.tokenCreationTime / tokenExpiryTime |
When Aura created / will expire the current deployment token (surfaced so an impending expiry is visible before it breaks registration) |
status.auraFleetManagement.tokenAutoRotate |
Whether Aura will auto-renew the token on expiry |
status.auraFleetManagement.servers[] / serverCount |
Aura's view of the servers reporting to this deployment (name, address, status, version, modeConstraint, lastPing, pluginVersion, licenseState, licenseType); a bounded summary — serverCount is the true total. Populated when provision.collectTelemetry is true |
status.auraFleetManagement.databases[] / databaseCount |
Aura's view of the databases (name, serverId, currentStatus, role, writer, lastCommittedTxn, replicationLag, graphShards, propertyShards); one row per reporting server. Populated when provision.collectTelemetry is true |
status.auraFleetManagement.telemetryError |
Why the last telemetry collection failed |
Example:
auraFleetManagement:
enabled: true
tokenSecretRef:
name: aura-fleet-token # kubectl create secret generic aura-fleet-token --from-literal=token='<token>'
key: token # defaults to "token"
See Aura Fleet Management Guide for full setup instructions.
SecretKeyRef (Token)¶
| Field | Type | Description |
|---|---|---|
name |
string |
Required. Name of the Kubernetes Secret containing the registration token |
key |
string |
Key within the Secret (default: "token") |
CrossClusterReplicationSpec¶
Enables network-mode cross-cluster database replication (CCDR): the operator
deploys a self-hosted TCP proxy (HAProxy, mode tcp, pure L4 passthrough —
it never terminates or inspects TLS) that exposes this cluster's tx-shipping
port (6000) externally via one LoadBalancer Service, and overrides
server.cluster.advertised_address per-server to the proxy's external
endpoint. server.cluster.raft.advertised_address (7000) and
server.routing.advertised_address (7688) are left untouched — RAFT leader
election never depends on this load balancer.
Opt-in tradeoff, stated plainly: enabling this means intra-cluster secondary catch-up traffic (which shares port 6000 with cross-cluster replica traffic) also routes through the load balancer, adding latency and LB-dependent cost proportional to write volume.
| Field | Type | Description |
|---|---|---|
enabled |
bool |
Deploy the proxy and override advertised addresses. Default false. |
loadBalancerInternal |
*bool |
Keep the proxy's Service on a private/internal load balancer where the cloud provider supports it. Default true — this design has no DNS split to keep intra-cluster traffic off the load balancer, so an internal LB is the main available mitigation against public exposure. |
annotations |
map[string]string |
Passed through to the proxy's Service, for cloud-specific load balancer tuning (e.g. the internal-LB annotation key, which differs per cloud provider). |
Status fields (read-only, set by the operator):
| Field | Description |
|---|---|
status.crossClusterReplication.ready |
true once the proxy's LoadBalancer Service has an assigned hostname/IP and every server's advertised address has been updated to route through it |
status.crossClusterReplication.loadBalancerHostname |
External hostname or IP assigned to the proxy's Service |
status.crossClusterReplication.addresses |
Ready-to-paste list for a downstream Neo4jReplicaDatabase's spec.source.addresses — one entry per server ordinal, <lb-hostname>:<port>. Only one entry is actually required on the downstream side (Neo4j redirects to the rest), but the full list is published so any entry can be used. |
Example — enable on the upstream cluster, then feed its status into a
downstream Neo4jReplicaDatabase in another Kubernetes cluster:
apiVersion: neo4j.neo4j.com/v1beta1
kind: Neo4jEnterpriseCluster
metadata: { name: prod }
spec:
acceptLicenseAgreement: "eval"
crossClusterReplication:
enabled: true
tls:
mode: cert-manager
issuerRef: { name: ca-cluster-issuer, kind: ClusterIssuer }
additionalClusterTrustCAs:
- name: dr-cluster-ca # the downstream cluster's CA Secret, copied here
---
# status.crossClusterReplication.addresses on `prod`, once ready, becomes
# spec.source.addresses on the downstream Neo4jReplicaDatabase — see
# docs/api_reference/neo4jreplicadatabase.md.
PropertyShardingSpec¶
Configures property sharding for horizontal scaling of large datasets. Property sharding separates graph structure from properties, distributing properties across multiple databases for better scalability. Available in Neo4j 2025.12+ Enterprise.
| Field | Type | Description |
|---|---|---|
enabled |
bool |
Enable property sharding for this cluster (default: false) |
config |
map[string]string |
Advanced property sharding configuration |
System Requirements (validated by operator):
- Neo4j Version: 2025.12+ Enterprise
- Minimum Servers: 2 servers (3+ recommended for HA graph shard primaries)
- Memory: 4GB minimum, 8GB+ recommended per server
- CPU: 1+ core minimum, 2+ cores recommended per server
- Authentication: Admin secret required
- Storage: Persistent storage class required
Required Configuration (automatically applied when enabled):
internal.dbms.sharded_property_database.enabled: "true"internal.dbms.sharded_property_database.allow_external_shard_access: "false"
Sharding does not set db.query.default_language: shard sub-databases inherit
the parent database's language, which the operator sets to Cypher 25 on every
sharded database. The server default is serverDefaultCypherLanguage's.
Performance Tuning Options:
propertySharding:
enabled: true
config:
# Transaction log retention (critical for shard sync)
db.tx_log.rotation.retention_policy: "14 days"
# Property synchronization interval
internal.dbms.sharded_property_database.property_pull_interval: "10ms"
# Memory optimization
server.memory.heap.max_size: "12G"
server.memory.pagecache.size: "6G"
# Connection pooling for cross-shard queries
server.bolt.thread_pool_min_size: "10"
server.bolt.thread_pool_max_size: "100"
Resource Recommendations:
Development:
resources:
requests:
memory: 4Gi # Absolute minimum for dev/test
cpu: 2000m # 2 cores for cross-shard queries
limits:
memory: 8Gi # Recommended for production
cpu: 2000m
Production:
resources:
requests:
memory: 4Gi # Minimum requirement
cpu: 2000m # Cross-shard performance
limits:
memory: 8Gi # Recommended for production
cpu: 4000m # Handle peak loads
Validation Errors:
| Error | Cause | Resolution |
|---|---|---|
property sharding requires Neo4j 2025.12+ |
Old Neo4j version | Upgrade to 2025.12+ Enterprise |
spec.topology.servers in body should be greater than or equal to 2 |
Invalid server count | Increase server count to 2+ (3+ recommended for HA) |
property sharding requires minimum 4GB memory |
Insufficient memory | Increase memory to 8GB+ (recommended) |
property sharding requires minimum 1 CPU core |
Insufficient CPU | Increase CPU to 2+ cores (recommended) |
For detailed configuration, see the Property Sharding Guide.
MonitoringSpec¶
Query performance monitoring and analytics configuration.
| Field | Type | Description |
|---|---|---|
enabled |
bool |
Enable query monitoring (default: true) |
slowQueryThreshold |
string |
Slow query threshold — maps to db.logs.query.threshold (default: "5s") |
explainPlan |
bool |
Enable query plan explanation in logs. Has performance impact — recommended false in production (default: false) |
queryLogLevel |
string |
Query log verbosity: OFF, INFO, or VERBOSE (default: "INFO") |
obfuscateLiterals |
bool |
Obfuscate literal values in query logs to protect sensitive data (default: false) |
metricsFilter |
string |
Glob pattern for which Neo4j metrics to expose, e.g. "*" for all (maps to server.metrics.filter) |
metricsPrefix |
string |
Custom prefix for Neo4j metric names (maps to server.metrics.prefix) |
sampling |
*QuerySamplingConfig |
Query sampling configuration |
metricsExport |
*QueryMetricsExportConfig |
Metrics export configuration |
logs |
*MonitoringLogsSpec |
Neo4j's own logs on the containers' standard output, as JSON, for a node-level log collector. Applies whether or not enabled is true. |
MonitoringLogsSpec¶
Neo4j writes query.log, security.log and debug.log to files under /logs; only neo4j.log reaches the container's standard output. Listing a log here also writes it to standard output, one JSON object per line, where an OpenTelemetry Collector or another node-level log collector picks it up with the pod's metadata. The files keep their usual formats. See OpenTelemetry.
| Field | Type | Description |
|---|---|---|
stdout |
[]string |
Logs to write to standard output: query (Neo4j's QueryLogJsonLayout), security (StructuredJsonLayout), debug (StructuredLayoutWithMessage). A set of up to three. |
The operator renders the log configuration (server-logs.xml) into the ConfigMap and points server.logs.config at the mounted copy:
- Turning it on or off restarts each server once.
server.logs.configis read at startup; aConfigNeedsRestartevent says so. - Changing the list afterwards restarts nothing. Neo4j re-reads the file it is pointed at every 30 seconds, and the kubelet updates the mount, so the change takes effect usually within two minutes.
spec.configmay not setserver.logs.configwhile a log is listed; validation refuses it.
QuerySamplingConfig¶
Query sampling configuration for performance monitoring.
| Field | Type | Description |
|---|---|---|
rate |
string |
Sampling rate as a decimal from 0 to 1 inclusive, pattern ^(0(\.\d+)?\|1(\.0+)?)$ (e.g. "0.5", "1.0", "0") |
maxQueriesPerSecond |
int32 |
Maximum queries to sample per second |
QueryMetricsExportConfig¶
Metrics export configuration for query monitoring.
| Field | Type | Description |
|---|---|---|
prometheus |
bool |
Export to Prometheus |
customEndpoint |
string |
Export to custom endpoint |
interval |
string |
Export interval |
PlacementConfig¶
Advanced placement and scheduling configuration.
| Field | Type | Description |
|---|---|---|
topologySpread |
*TopologySpreadConfig |
Topology spread constraints |
antiAffinity |
*PodAntiAffinityConfig |
Pod anti-affinity rules |
nodeSelector |
map[string]string |
Reserved — no effect today. Never read by the topology scheduler; use the top-level spec.nodeSelector. A ValidationWarning event is raised when it is set. |
requiredDuringScheduling |
bool |
Reserved — no effect today. Never read; use antiAffinity.type: required or topology.enforceDistribution for hard placement. A ValidationWarning event is raised when it is true. |
TopologySpreadConfig¶
Controls how servers are distributed across cluster topology.
| Field | Type | Description |
|---|---|---|
enabled |
bool |
Enable topology spread constraints |
topologyKey |
string |
Topology domain (e.g., "topology.kubernetes.io/zone") |
maxSkew |
int32 |
Maximum allowed imbalance between domains |
whenUnsatisfiable |
string |
Action when constraints can't be satisfied |
minDomains |
*int32 |
Minimum number of eligible domains |
PodAntiAffinityConfig¶
Prevents servers from being scheduled on the same nodes/zones.
| Field | Type | Description |
|---|---|---|
enabled |
bool |
Enable anti-affinity rules |
topologyKey |
string |
Anti-affinity topology domain |
type |
string |
Constraint type: "required" or "preferred" |
ServiceSpec¶
Configures how Neo4j is exposed outside the Kubernetes cluster.
| Field | Type | Description |
|---|---|---|
type |
string |
Service type: "ClusterIP", "NodePort", "LoadBalancer" (default: "ClusterIP") |
annotations |
map[string]string |
Service annotations (e.g., for cloud load balancer configuration) |
loadBalancerIP |
string |
Static IP for LoadBalancer service (cloud provider specific) |
loadBalancerSourceRanges |
[]string |
IP ranges allowed to access LoadBalancer |
externalTrafficPolicy |
string |
External traffic policy: "Cluster" or "Local" |
dnsName |
string |
Public DNS hostname (e.g. "neo4j.example.com"). Surfaces as the external-dns.alpha.kubernetes.io/hostname annotation on the front-facing Service and (when enabled) the Ingress so external-dns creates the matching cloud DNS record; also added to the cert-manager Certificate SAN list when spec.tls is enabled. See the External Access guide. |
ingress |
IngressSpec |
Ingress configuration |
route |
RouteSpec |
OpenShift Route configuration |
AuditSpec¶
Compliance-oriented logging configuration. Neo4j 5.x / 2025.x has no
unified dbms.security.audit.* block (those were 4.x keys, removed) —
"audit logging" in modern Neo4j is the combination of security.log
(controlled by dbms.security.*) and query.log (controlled by
db.logs.query.*). This spec exposes the audit-relevant subset of
those keys as typed fields.
| Field | Type | Neo4j Config Key | Description |
|---|---|---|---|
enabled |
bool |
— | Master flag. When true AND obfuscateQueryLiterals is unset, the operator defaults the emitted obfuscation value to true (secure-by-default). Default false. |
logSuccessfulAuthentication |
*bool |
dbms.security.log_successful_authentication |
When false, only FAILED logins appear in security.log. Unset → Neo4j default (true) applies. |
obfuscateQueryLiterals |
*bool |
db.logs.query.obfuscate_literals |
Redact literal values in query.log. Strongly recommended for PCI / HIPAA / GDPR. Unset + enabled=true → defaults to true. Doesn't redact node labels, relationship types, or property keys. |
parameterLogging |
*bool |
db.logs.query.parameter_logging_enabled |
Include parameter VALUES in query.log. Set false when parameter values are sensitive. Unset → Neo4j default (true) applies. |
Overlap with MonitoringSpec: both touch
db.logs.query.obfuscate_literals. When set on both, spec.audit wins
— the operator emits monitoring first, then audit, and Neo4j's
last-write-wins config semantics give audit the final value. User
spec.config overrides (appended last) still win over both.
NetworkPolicySpec¶
Controls operator emission of a Kubernetes NetworkPolicy that hardens ingress to the Neo4j server pods. Most importantly, closes the backup port (6362) to non-backup pods.
| Field | Type | Description |
|---|---|---|
enabled |
bool |
When true, the operator emits a NetworkPolicy named <cluster>-server-netpol (cluster) or <standalone>-standalone-netpol (standalone). Public client ports (7474/7473/7687) remain open to any pod; intra-cluster ports (6000/7000/7688) are restricted to peer servers; backup port (6362) is restricted to operator-managed backup pods. Default false. |
allowReplicasFrom |
[]NetworkPolicyPeerCluster |
Opt-in: additively admits named downstream Neo4jEnterpriseCluster(s) on port 6000 only (never RAFT/routing), for a same-Kubernetes-cluster network-mode CCDR replica that would otherwise be blocked by the peer-restriction rule (which only admits this cluster's own neo4j.com/cluster label). Nothing is admitted unless listed here. Irrelevant to backup-mode replication, which has no network coupling to restrict. |
NetworkPolicyPeerCluster¶
| Field | Type | Description |
|---|---|---|
name |
string |
Required. Name of the downstream Neo4jEnterpriseCluster to admit. |
namespace |
string |
Namespace of the downstream Neo4jEnterpriseCluster. Defaults to this cluster's own namespace when omitted. |
Scoped by both a podSelector (neo4j.com/cluster: <name>) and a namespaceSelector
(Kubernetes' well-known kubernetes.io/metadata.name label) — a podSelector alone would
match a same-named cluster in any namespace.
CNI prerequisite: NetworkPolicy is enforced only by Calico, Cilium, Antrea, Weave, and most managed offerings. Flannel does NOT enforce NetworkPolicy — enabling this on a flannel cluster creates the resource but has no effect on traffic. See Network Security for details.
IngressSpec¶
Configures an Ingress resource for HTTP(S) access to Neo4j Browser.
| Field | Type | Description |
|---|---|---|
enabled |
bool |
Enable Ingress creation |
className |
string |
Ingress class name (e.g., "nginx") |
annotations |
map[string]string |
Ingress annotations |
host |
string |
Hostname for the Ingress |
tlsSecretName |
string |
TLS secret name for Ingress |
RouteSpec¶
| Field | Type | Description |
|---|---|---|
enabled |
bool |
Enable OpenShift Route creation |
host |
string |
Hostname for the Route (optional) |
path |
string |
Path for the Route (default: "/") |
annotations |
map[string]string |
Route annotations |
tls |
*RouteTLSSpec |
TLS settings for the Route |
targetPort |
int32 |
Target service port (default: 7474) |
RouteTLSSpec¶
| Field | Type | Description |
|---|---|---|
termination |
string |
TLS termination: "edge", "reencrypt", "passthrough" |
insecureEdgeTerminationPolicy |
string |
"None", "Allow", "Redirect" |
secretName |
string |
Secret containing certificate (reencrypt/passthrough) |
MCPServerSpec¶
Optional MCP server deployment for the cluster using the official mcp/neo4j image (github.com/neo4j/mcp). Requires the APOC plugin for the get-schema tool.
HTTP transport (default): no credentials stored in the pod — each client request carries a Basic Auth or Bearer token Authorization header. A Service is created at <name>-mcp:8080; the endpoint path is /mcp (fixed).
STDIO transport: NEO4J_USERNAME and NEO4J_PASSWORD are injected from the admin secret (or a custom secret via spec.mcp.auth). No Service is created.
For client configuration, see the MCP Client Setup Guide.
| Field | Type | Description |
|---|---|---|
enabled |
bool |
Enable MCP server deployment (default: false) |
image |
*ImageSpec |
MCP server image. Defaults to mcp/neo4j:latest. Pin a version with image.tag. |
transport |
string |
Transport mode: "http" (default) or "stdio" |
readOnly |
bool |
Disable the write-cypher tool when true (default: true) |
database |
string |
Default Neo4j database for MCP queries |
schemaSampleSize |
*int32 |
Schema sampling size for get-schema. Use -1 to sample entire graph. |
telemetry |
*bool |
Enable anonymous usage telemetry sent to Neo4j. When unset, uses the server default (true). Set to false to opt out. |
logLevel |
string |
Log verbosity: debug, info, notice, warning, error, critical, alert, emergency |
logFormat |
string |
Log output format: text (default) or json |
http |
*MCPHTTPConfig |
HTTP transport configuration (only used when transport: http) |
auth |
*MCPAuthSpec |
Override Neo4j credentials for STDIO transport. Ignored in HTTP mode (credentials come per-request from the client). |
replicas |
*int32 |
Number of MCP pod replicas (default: 1). Only meaningful for HTTP. |
resources |
*corev1.ResourceRequirements |
Resource requirements for MCP pods |
env |
[]corev1.EnvVar |
Extra environment variables. Operator-managed vars (NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD, NEO4J_TRANSPORT_MODE, NEO4J_MCP_HTTP_*, etc.) are silently ignored. |
securityContext |
*SecurityContextSpec |
Pod/container security context overrides |
MCPHTTPConfig¶
HTTP transport configuration. Only applied when spec.mcp.transport: http.
| Field | Type | Description |
|---|---|---|
host |
string |
HTTP bind host (default: 0.0.0.0) |
port |
int32 |
HTTP bind port. Default: 8080 without TLS, 8443 with TLS. |
tls |
*MCPTLSSpec |
Optional container-level TLS. When unset, handle TLS at the Ingress layer instead. |
authHeaderName |
string |
Name of the HTTP header carrying credentials (default: Authorization). Override when a proxy rewrites the standard header. |
service |
*MCPServiceSpec |
Kubernetes Service and Ingress/Route exposure settings |
MCPTLSSpec¶
Enables container-level TLS on the mcp/neo4j HTTP server by mounting a Kubernetes TLS secret and injecting NEO4J_MCP_HTTP_TLS_ENABLED=true with the cert/key file paths. For most deployments, Ingress-level TLS termination is simpler.
| Field | Type | Description |
|---|---|---|
secretName |
string |
Required. Name of a Kubernetes TLS secret (type: kubernetes.io/tls) containing the certificate and private key. |
certKey |
string |
Key in the secret for the certificate file (default: tls.crt) |
keyKey |
string |
Key in the secret for the private key file (default: tls.key) |
MCPServiceSpec¶
| Field | Type | Description |
|---|---|---|
type |
string |
Service type: "ClusterIP" (default), "NodePort", "LoadBalancer" |
annotations |
map[string]string |
Service annotations |
loadBalancerIP |
string |
Static LoadBalancer IP |
loadBalancerSourceRanges |
[]string |
Allowed source ranges |
externalTrafficPolicy |
string |
External traffic policy: "Cluster" or "Local" |
port |
int32 |
Service port for MCP HTTP (default: same as container port) |
ingress |
*IngressSpec |
Ingress configuration. Path is always /mcp (fixed by the official image). |
route |
*RouteSpec |
OpenShift Route configuration |
MCPAuthSpec¶
Overrides the Neo4j credentials injected into the MCP pod. Only effective for STDIO transport. In HTTP mode the server authenticates per-request using the client's Authorization header.
| Field | Type | Description |
|---|---|---|
secretName |
string |
Secret containing username/password keys. Defaults to the cluster admin secret when unset. |
usernameKey |
string |
Username key name (default: username) |
passwordKey |
string |
Password key name (default: password) |
UpgradeStrategySpec¶
Configures how the operator handles Neo4j version upgrades.
| Field | Type | Description |
|---|---|---|
strategy |
string |
Upgrade strategy: "RollingUpgrade" (default) or "Recreate" |
preUpgradeHealthCheck |
bool |
Enable health validation before upgrade (default: true) |
maxUnavailableDuringUpgrade |
*int32 |
Accepted but has no effect: the operator restarts one server at a time, as Neo4j's rolling upgrade requires, and waits for its databases to catch up before the next. A value other than 1 (the default) raises a ValidationWarning |
upgradeTimeout |
string |
Timeout for the entire upgrade process (default: "30m") |
postUpgradeHealthCheck |
bool |
Enable health validation after upgrade (default: true) |
healthCheckTimeout |
string |
Timeout for health checks (default: "5m") |
stabilizationTimeout |
string |
Wait time for cluster stabilization (default: "3m") |
autoPauseOnFailure |
bool |
Pause upgrade on failure for manual intervention (default: true) |
Status¶
The Neo4jEnterpriseClusterStatus represents the observed state of the cluster.
| Field | Type | Description |
|---|---|---|
phase |
string |
Cluster phase: "Initializing", "Forming", "Ready", "Degraded" (formed and serving with a majority of its servers, one or more unavailable for longer than the grace period — see Server availability), "Expanding" (storage expansion in progress), "Paused" (upgrade paused after a failure), "Failed" |
ready |
bool |
true only in phase Ready; false in phase Degraded, though the cluster still serves |
message |
string |
Human-readable status message |
conditions |
[]metav1.Condition |
Cluster conditions (e.g. Ready, Degraded, ClusterFormed, ServersHealthy, DatabasesHealthy, ServersPendingDrain) — see Conditions |
replicas |
*ReplicaStatus |
Server counts: servers (desired) and ready |
endpoints |
EndpointStatus |
Service endpoints |
version |
string |
Current Neo4j version |
upgradeStatus |
*UpgradeStatus |
Detailed upgrade progress information |
lastUpgradeTime |
*metav1.Time |
When the last upgrade was performed |
propertyShardingReady |
bool |
Whether property sharding is configured and ready (Neo4j 2025.12+) |
effectiveCypherLanguage |
string |
The server default Cypher version the operator resolved (see spec.serverDefaultCypherLanguage), recorded once so an unset spec never changes meaning under a running cluster |
auraFleetManagement |
object |
State of the Aura Fleet Management integration (when spec.auraFleetManagement.enabled=true) |
observedGeneration |
int64 |
Last observed generation |
diagnostics |
*DiagnosticsStatus |
Live diagnostics, collected once the cluster has formed (also while it is degraded) unless spec.monitoring.enabled: false. |
crossClusterReplication |
*CrossClusterReplicationStatus |
State of the network-mode CCDR exposure proxy, when spec.crossClusterReplication.enabled is true. |
internalAddresses |
[]string |
Ready-to-paste <pod-fqdn>:6000 list for a downstream Neo4jReplicaDatabase on this same Kubernetes cluster (source.upstreamClusterRef, or pasted directly into source.addresses). Always populated — no proxy, no LoadBalancer needed — but not routable from a genuinely separate Kubernetes cluster; use crossClusterReplication for that. |
EndpointStatus¶
Service endpoints and connection information populated by the controller on
every status update. Schema is shared between Neo4jEnterpriseCluster and
Neo4jEnterpriseStandalone; both controllers resolve URLs against the
{name}-client ClusterIP (on a standalone, {name}-service also resolves but
is deprecated; use {name}-client).
| Field | Type | Description |
|---|---|---|
bolt |
string |
Bolt protocol endpoint (e.g. bolt+s://<service>:7687 when TLS is enabled, bolt://... when disabled) |
http |
string |
HTTP endpoint for Neo4j Browser |
https |
string |
HTTPS endpoint for Neo4j Browser |
internal |
InternalEndpoints |
In-cluster service FQDNs (headless + client; cluster only) |
connectionExamples |
ConnectionExamples |
Ready-to-paste connection strings driven by spec.service.type (ClusterIP / NodePort / LoadBalancer) |
ReplicaStatus¶
| Field | Type | Description |
|---|---|---|
servers |
int32 |
Number of servers |
ready |
int32 |
Number of ready servers |
InternalEndpoints¶
| Field | Type | Description |
|---|---|---|
headless |
string |
Headless service FQDN used for pod-level discovery |
client |
string |
Client service FQDN used for client connections |
ConnectionExamples¶
Connection-string snippets generated from spec.service.type and the
LoadBalancer-assigned external IP (when applicable). On a LoadBalancer
service the external IP is filled in once the cloud provider's controller
has assigned one; until then a <external-ip> placeholder is substituted.
For NodePort the bolt port and HTTPS port appear as <bolt-node-port> /
<http-node-port> placeholders since those are dynamically assigned and
visible via kubectl get svc.
| Field | Type | Description |
|---|---|---|
portForward |
string |
kubectl port-forward command (always available) |
browserURL |
string |
Neo4j Browser URL |
boltURI |
string |
Bolt connection URI (uses bolt+ssc:// when TLS is enabled, since cert-manager-issued certs typically aren't trusted by external clients) |
neo4jURI |
string |
Neo4j routing-driver URI |
pythonExample |
string |
Python driver connection snippet |
javaExample |
string |
Java driver connection snippet |
UpgradeStatus¶
Detailed upgrade progress tracking.
| Field | Type | Description |
|---|---|---|
phase |
string |
Upgrade state-machine phase (schema enum Pending, Staging, InProgress, Rolling, Stabilizing, Verifying, Paused, Completed, Failed): Staging (target pod template applied, pod restarts frozen) → Rolling (servers restarted one at a time, highest ordinal first) → Stabilizing (health, consensus and replication gate) → Verifying (per-server version check) → Completed. Paused and Failed are terminal until operator/user action. InProgress is resumed as Staging; Pending is reserved |
startTime |
*metav1.Time |
When the upgrade started |
stepStartTime |
*metav1.Time |
Anchor for the per-step timeout (upgradeTimeout per Rolling step, stabilizationTimeout, healthCheckTimeout); reset on every phase transition and partition advance |
phaseStartTime |
*metav1.Time |
When the current phase began; reset only when the phase changes (unlike stepStartTime, which also restarts on every partition advance inside Rolling). It is the anchor neo4j_operator_upgrade_duration_seconds is measured from |
currentPartition |
*int32 |
StatefulSet RollingUpdate partition most recently applied; servers with ordinal >= partition have been rolled to the target version. Used to resume after an operator restart |
completionTime |
*metav1.Time |
When the upgrade completed |
currentStep |
string |
Current upgrade step description |
previousVersion |
string |
Version before upgrade |
targetVersion |
string |
Version being upgraded to |
progress |
*UpgradeProgress |
Upgrade progress statistics |
message |
string |
Additional upgrade details |
lastError |
string |
Last error encountered during upgrade |
UpgradeProgress¶
Upgrade progress across servers.
| Field | Type | Description |
|---|---|---|
total |
int32 |
Total number of servers to upgrade |
upgraded |
int32 |
Number of servers successfully upgraded |
inProgress |
int32 |
Number of servers currently being upgraded |
pending |
int32 |
Number of servers pending upgrade |
servers |
*NodeUpgradeProgress |
Server upgrade details |
NodeUpgradeProgress¶
Server-specific upgrade progress.
| Field | Type | Description |
|---|---|---|
total |
int32 |
Total number of servers |
upgraded |
int32 |
Number of servers successfully upgraded |
inProgress |
int32 |
Number of servers currently being upgraded |
pending |
int32 |
Number of servers pending upgrade |
currentLeader |
string |
Current leader server |
DiagnosticsStatus¶
Live diagnostics collected from SHOW SERVERS and SHOW DATABASES once the cluster has formed, including while it is degraded with a server down. On by default; spec.monitoring.enabled: false turns it off. Collected on every reconcile and written to status when something in them changes, or at least every 5 minutes (see lastCollected).
| Field | Type | Description |
|---|---|---|
users |
[]UserDiagnosticInfo |
Users currently in the system database (SHOW USERS): user, roles, suspended, homeDatabase. A bounded summary — see userCount. |
userCount |
int |
Total number of users observed, even when users is truncated. |
roles |
[]RoleDiagnosticInfo |
Roles currently in the system database (SHOW ROLES): role, immutable. A bounded summary — see roleCount. |
roleCount |
int |
Total number of roles observed, even when roles is truncated. |
servers |
[]ServerDiagnostic |
SHOW SERVERS results. Each entry has name, address, state, health, hostingDatabases. |
servers[].name |
string |
Server display name. |
servers[].address |
string |
Bolt address of the server. |
servers[].state |
string |
Lifecycle state: Enabled, Cordoned, Deallocating. |
servers[].health |
string |
Health status: Available or Unavailable. |
servers[].hostingDatabases |
int |
Number of databases currently hosted on this server. |
databases |
[]DatabaseDiagnostic |
SHOW DATABASES results. Each entry has name, status, requestedStatus, role, default, plus type (system, standard, composite, graph shard/property shard on 2025.12+, replica on 2026.08+), access (read-write/read-only), writer, lastCommittedTxn and replicationLag (for a cross-cluster replica, the data loss that promoting now would make permanent). |
databases[].name |
string |
Database name. |
databases[].status |
string |
Current status: online, offline, quarantined. |
databases[].requestedStatus |
string |
Desired status as requested by the operator. |
databases[].role |
string |
Role on the last-contacted server: primary, secondary. |
databases[].default |
bool |
Whether this is the cluster's default database. |
lastCollected |
string (RFC3339) |
When the diagnostics were last written. They are collected on every reconcile but written only when something in them changes (a server, a database, the users, the roles, an error) or at least every 5 minutes, so this is at most 5 minutes old while the cluster is reconciling. The databases' lastCommittedTxn and replicationLag move with every write and do not count as a change: they are a snapshot from the last write. |
collectionError |
string |
Error message from the last failed collection; empty on success. |
Conditions¶
The operator maintains the following condition types on status.conditions (standard metav1.Condition):
| Condition Type | True when |
False when |
Unknown when |
|---|---|---|---|
Degraded |
The cluster has formed and one or more of its servers is unavailable (reason ServersUnavailable; the message names them). Its lastTransitionTime is when the shortfall began |
Every server is available (reason AllServersAvailable) |
n/a — absent until the cluster first forms |
ClusterFormed |
The cluster has formed at least once. Never set back to False |
n/a | n/a — absent until the cluster first forms |
ServersHealthy |
All servers are state=Enabled and health=Available |
Any server is Cordoned, Deallocating, or Unavailable | Diagnostics cannot be collected (cluster not formed yet, or Bolt unreachable) |
DatabasesHealthy |
All user databases have status=online |
Any database has requestedStatus=online but status≠online |
Diagnostics cannot be collected (cluster not formed yet, or Bolt unreachable) |
ServersPendingDrain |
Set during a scale-down: the cluster still has Neo4j servers registered beyond spec.topology.servers (removed servers not yet deallocated and dropped), so databases may be under-replicated on them |
The removed servers have been drained and dropped | n/a |
RestartPending |
One or more servers have not restarted since the operator added a setting it applies at each server's next restart instead of restarting for it (reason SettingsAwaitRestart; the message names the servers and settings). Today: dbms.cluster.raft.async_channel_acquisition_enabled on 5.26.29+ (see 5.26: writes while a server is stopped), and dbms.security.tls_reload_enabled on CalVer 2025.03+ with cert-manager TLS (see TLS certificate rotation). Restart the named pods, or let it happen on their next restart |
Every server runs those settings (reason NoSettingsAwaitRestart) |
n/a — absent when nothing is applied at restart (CalVer, or the setting is set in spec.config) |
CrossClusterProxySecure |
The cluster SSL policy requires a peer certificate on the port the CCDR proxy publishes | strictPeerValidation is false, so trust_all=true accepts any peer certificate — the exposed port is encrypted but not authenticated. (A cluster with no TLS at all cannot reach this state: the proxy is rejected at validation.) |
n/a — the condition is removed entirely when the proxy is disabled |
Note: The
systemdatabase is excluded from theDatabasesHealthycheck because it has special internal lifecycle behavior.
Server availability¶
A cluster is Forming until every server has joined it once. After that, a missing server — a pod restart, an OOM kill, a node drain — is an outage of a formed cluster, and the cluster is judged on whether it is still serving:
| Situation | Phase | Ready condition |
Degraded condition |
Dependents¹ |
|---|---|---|---|---|
| Every server available | Ready |
True |
False |
proceed |
| A majority available, short for less than the grace period | Ready |
True |
True |
proceed |
| A majority available, short for longer than the grace period | Degraded |
False (ClusterDegraded) |
True |
proceed |
| Half or fewer available | Forming |
Unknown |
True |
pause |
| First formation, a scale-up, or a rollout of the server StatefulSet | Forming |
Unknown |
unchanged | pause |
¹ Neo4jUser, Neo4jRole, Neo4jRoleBinding, Neo4jAuthRule, Neo4jDatabase, Neo4jShardedDatabase, Neo4jDatabaseAlias, Neo4jCompositeDatabase, Neo4jReplicaDatabase, Neo4jReplicaPromotion and Neo4jBackup. Whether a statement can run on the servers that are up is Neo4j's call, reported on that resource. Operations that restart every server — a rolling image upgrade, a Neo4jPlugin install — wait until every server is back.
The grace period defaults to 5 minutes and is set with the operator flag --server-unavailable-grace (Helm: serverUnavailableGrace). Raise it if your servers take longer than that to restart and rejoin. Entering Degraded raises one ClusterDegraded Warning event; losing a majority raises one ClusterQuorumLost Warning. ServersHealthy, DatabasesHealthy and the neo4j_operator_server_health metric keep updating throughout.
Examples¶
Basic Cluster¶
apiVersion: neo4j.neo4j.com/v1beta1
kind: Neo4jEnterpriseCluster
metadata:
name: basic-cluster
spec:
acceptLicenseAgreement: "eval"
image:
repo: neo4j # Note: field name is 'repo', not 'repository'
tag: "5.26-enterprise"
topology:
servers: 3 # Creates StatefulSet basic-cluster-server with 3 replicas
storage:
className: standard
size: 10Gi
auth:
adminSecret: neo4j-admin-secret # Note: field name is 'adminSecret'
resources:
requests:
cpu: "1"
memory: 4Gi
limits:
cpu: "2"
memory: 8Gi
Cluster with Server Role Constraints¶
apiVersion: neo4j.neo4j.com/v1beta1
kind: Neo4jEnterpriseCluster
metadata:
name: role-constrained-cluster
spec:
acceptLicenseAgreement: "eval"
image:
repo: neo4j
tag: "5.26-enterprise"
topology:
servers: 5
# Global constraint: all servers default to any role
serverModeConstraint: NONE
# Per-server role hints (overrides global constraint)
serverRoles:
- serverIndex: 0
modeConstraint: PRIMARY # Server-0: only primary databases
- serverIndex: 1
modeConstraint: PRIMARY # Server-1: only primary databases
- serverIndex: 2
modeConstraint: SECONDARY # Server-2: only secondary databases
- serverIndex: 3
modeConstraint: SECONDARY # Server-3: only secondary databases
- serverIndex: 4
modeConstraint: NONE # Server-4: any database mode
# Advanced placement for high availability
placement:
topologySpread:
enabled: true
topologyKey: topology.kubernetes.io/zone
maxSkew: 1
whenUnsatisfiable: DoNotSchedule
antiAffinity:
enabled: true
topologyKey: kubernetes.io/hostname
type: required
availabilityZones:
- us-east-1a
- us-east-1b
- us-east-1c
enforceDistribution: true
storage:
className: fast-ssd
size: 50Gi
auth:
adminSecret: neo4j-admin-secret
Cluster with Query Monitoring¶
Backups are configured separately via the
Neo4jBackupCRD — see the next example.
apiVersion: neo4j.neo4j.com/v1beta1
kind: Neo4jEnterpriseCluster
metadata:
name: monitored-cluster
spec:
acceptLicenseAgreement: "eval"
image:
repo: neo4j
tag: "5.26-enterprise"
topology:
servers: 3 # Creates monitored-cluster-server StatefulSet
storage:
className: fast-ssd
size: 50Gi
auth:
adminSecret: neo4j-admin-secret
# Enhanced query monitoring
monitoring:
enabled: true
slowQueryThreshold: "1s"
queryLogLevel: "INFO"
obfuscateLiterals: true
sampling:
rate: "0.1"
maxQueriesPerSecond: 100
metricsExport:
prometheus: true
interval: "30s"
Create Scheduled Backup (Separate Resource)¶
# Backups are managed via the Neo4jBackup CRD
apiVersion: neo4j.neo4j.com/v1beta1
kind: Neo4jBackup
metadata:
name: daily-cluster-backup
spec:
instanceRef: monitored-cluster # the Neo4jEnterpriseCluster (or Standalone) to back up
allDatabases: true # or `database: <name>` for a single database
storage:
type: s3
bucket: neo4j-backups
path: daily/
schedule: "0 2 * * *" # Daily at 2 AM
retention:
maxAge: "30d"
maxCount: 30
options:
compress: true
backupType: FULL
Cluster with LoadBalancer Service¶
apiVersion: neo4j.neo4j.com/v1beta1
kind: Neo4jEnterpriseCluster
metadata:
name: public-cluster
spec:
acceptLicenseAgreement: "eval"
image:
repo: neo4j
tag: "5.26-enterprise"
topology:
servers: 5 # Creates public-cluster-server StatefulSet with 5 replicas
storage:
className: standard
size: 20Gi
auth:
adminSecret: neo4j-admin-secret
# LoadBalancer service configuration
service:
type: LoadBalancer
annotations:
# AWS NLB example
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
loadBalancerSourceRanges:
- "10.0.0.0/8" # Corporate network
- "172.16.0.0/12" # VPN range
externalTrafficPolicy: Local # Preserve client IPs
Cluster with Ingress¶
apiVersion: neo4j.neo4j.com/v1beta1
kind: Neo4jEnterpriseCluster
metadata:
name: ingress-cluster
spec:
acceptLicenseAgreement: "eval"
image:
repo: neo4j
tag: "5.26-enterprise"
topology:
servers: 3 # Creates ingress-cluster-server StatefulSet
storage:
className: fast-ssd
size: 20Gi
auth:
adminSecret: neo4j-admin-secret
# TLS configuration
tls:
mode: cert-manager
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
# Ingress configuration
service:
ingress:
enabled: true
className: nginx
host: neo4j.example.com
tlsSecretName: neo4j-tls
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
Management Commands¶
Basic Operations¶
# Create a cluster
kubectl apply -f cluster.yaml
# List clusters
kubectl get neo4jenterprisecluster
# Get cluster details
kubectl describe neo4jenterprisecluster my-cluster
# Check cluster status
kubectl get neo4jenterprisecluster my-cluster -o yaml
# Port forward for local access
kubectl port-forward svc/my-cluster-client 7474:7474 7687:7687
Cluster Operations¶
# Scale cluster (change server count)
kubectl patch neo4jenterprisecluster my-cluster -p '{"spec":{"topology":{"servers":5}}}'
# Update Neo4j version
kubectl patch neo4jenterprisecluster my-cluster -p '{"spec":{"image":{"tag":"2025.01.0-enterprise"}}}'
# Check cluster health
kubectl exec my-cluster-server-0 -- cypher-shell -u neo4j -p password "SHOW SERVERS"
# Monitor server pods
kubectl get pods -l neo4j.com/cluster=my-cluster
kubectl logs my-cluster-server-0 -c neo4j
Usage Patterns¶
Multi-Database Architecture¶
# 1. Create cluster with role-optimized servers
apiVersion: neo4j.neo4j.com/v1beta1
kind: Neo4jEnterpriseCluster
metadata:
name: multi-db-cluster
spec:
acceptLicenseAgreement: "eval"
topology:
servers: 6
serverRoles:
- serverIndex: 0
modeConstraint: PRIMARY # Dedicated for write workloads
- serverIndex: 1
modeConstraint: PRIMARY
- serverIndex: 2
modeConstraint: SECONDARY # Dedicated for read workloads
- serverIndex: 3
modeConstraint: SECONDARY
- serverIndex: 4
modeConstraint: NONE # Mixed workloads
- serverIndex: 5
modeConstraint: NONE
# ... other configuration
---
# 2. Create databases with specific topologies
apiVersion: neo4j.neo4j.com/v1beta1
kind: Neo4jDatabase
metadata:
name: user-database
spec:
clusterRef: multi-db-cluster
name: users
topology:
primaries: 2 # Uses servers 0-1 (PRIMARY constraint)
secondaries: 2 # Uses servers 2-3 (SECONDARY constraint)
---
apiVersion: neo4j.neo4j.com/v1beta1
kind: Neo4jDatabase
metadata:
name: analytics-database
spec:
clusterRef: multi-db-cluster
name: analytics
topology:
primaries: 1 # Uses server 4 or 5 (NONE constraint)
secondaries: 3 # Uses remaining servers
Development vs Production¶
The
dev-clusterandprod-clustermanifests below omit the requiredspec.image(andauth, where unset) for brevity — add them as in the examples above.
# Development cluster (minimal resources)
apiVersion: neo4j.neo4j.com/v1beta1
kind: Neo4jEnterpriseCluster
metadata:
name: dev-cluster
spec:
acceptLicenseAgreement: "eval"
topology:
servers: 2 # Minimum for clustering
storage:
className: standard
size: 10Gi
resources:
requests:
cpu: 200m
memory: 1Gi
limits:
cpu: 1
memory: 2Gi
tls:
mode: disabled
---
# Property Sharding cluster (Neo4j 2025.12+)
apiVersion: neo4j.neo4j.com/v1beta1
kind: Neo4jEnterpriseCluster
metadata:
name: sharding-cluster
spec:
acceptLicenseAgreement: "eval"
image:
repo: neo4j
tag: 2025.12-enterprise
topology:
servers: 7 # Sufficient for property sharding workloads
storage:
className: fast-ssd
size: 100Gi
resources:
requests:
cpu: 2
memory: 8Gi
limits:
cpu: 4
memory: 16Gi
propertySharding:
enabled: true
config:
db.tx_log.rotation.retention_policy: "14 days"
internal.dbms.sharded_property_database.property_pull_interval: "5ms"
server.memory.heap.max_size: "12G"
server.memory.pagecache.size: "6G"
tls:
mode: cert-manager
issuerRef:
name: ca-cluster-issuer
kind: ClusterIssuer
---
# Production cluster (high availability)
apiVersion: neo4j.neo4j.com/v1beta1
kind: Neo4jEnterpriseCluster
metadata:
name: prod-cluster
spec:
acceptLicenseAgreement: "eval"
topology:
servers: 5
placement:
topologySpread:
enabled: true
topologyKey: topology.kubernetes.io/zone
maxSkew: 1
antiAffinity:
enabled: true
type: required
availabilityZones: [us-east-1a, us-east-1b, us-east-1c]
enforceDistribution: true
storage:
className: fast-ssd
size: 100Gi
resources:
requests:
cpu: 2
memory: 8Gi
limits:
cpu: 4
memory: 16Gi
tls:
mode: cert-manager
issuerRef:
name: ca-cluster-issuer
kind: ClusterIssuer
auth:
adminSecret: neo4j-admin-secret
Best Practices¶
- Resource Planning: Allocate sufficient memory (≥4Gi) and CPU for Neo4j Enterprise workloads
- High Availability: Use odd number of servers (3, 5) and distribute across availability zones
- Server Role Optimization: Use role constraints to optimize server usage for specific workload patterns
- Storage: Use fast SSD storage classes (
fast-ssd,premium-ssd) for production workloads - Security: Always enable TLS and use strong password policies in production
- Monitoring: Enable query monitoring and centralized logging for performance insights
- Backup Strategy: Define a
Neo4jBackupCR per backup target (one-shot or scheduled viaspec.schedule) with appropriateretentionpolicies - Scaling: Plan for growth - scaling up is easier than scaling down
Troubleshooting¶
Common Issues¶
# Check cluster formation
kubectl exec my-cluster-server-0 -- cypher-shell -u neo4j -p password "SHOW SERVERS"
# Check pod status
kubectl get pods -l neo4j.com/cluster=my-cluster
kubectl describe pod my-cluster-server-0
# Check operator logs
kubectl logs -n neo4j-operator-system -l control-plane=controller-manager --tail=-1
# Verify split-brain detection
kubectl get events --field-selector reason=SplitBrainDetected
Resource Conflicts¶
# Force reconciliation with a no-op annotation change
kubectl annotate neo4jenterprisecluster my-cluster \
troubleshooting.neo4j.com/reconcile="$(date +%s)" --overwrite
For more information: