Skip to content

Installation Guide

This guide provides detailed instructions for installing the Neo4j Enterprise Operator for Kubernetes.

Quick Installation

The chart repository is hosted at https://priyolahiri.github.io/neo4j-kubernetes-operator/charts and is updated automatically on every operator release.

helm repo add neo4j-operator https://priyolahiri.github.io/neo4j-kubernetes-operator/charts
helm repo update

helm install neo4j-operator neo4j-operator/neo4j-operator \
  --namespace neo4j-operator-system \
  --create-namespace

Pin to a specific version:

helm install neo4j-operator neo4j-operator/neo4j-operator \
  --version 1.21.1 \
  --namespace neo4j-operator-system \
  --create-namespace

Customise installation values:

# View available configuration options
helm show values neo4j-operator/neo4j-operator

helm install neo4j-operator neo4j-operator/neo4j-operator \
  --namespace neo4j-operator-system \
  --create-namespace \
  --set resources.limits.memory=512Mi

Upgrade:

Apply the new release's CRDs before helm upgrade — this step is mandatory. Helm installs the contents of a chart's crds/ directory only on first install and never upgrades them; helm upgrade silently leaves the old CRDs in place. Running a new operator against stale CRD schemas means any fields added in the new release are silently pruned by the API server — your manifests apply cleanly but the new fields never reach the operator.

# 1. Refresh the CRDs from the release you are upgrading to (mandatory)
kubectl apply --server-side -f https://github.com/priyolahiri/neo4j-kubernetes-operator/releases/download/<version>/neo4j-kubernetes-operator.yaml

# 2. Upgrade the chart
helm repo update
helm upgrade neo4j-operator neo4j-operator/neo4j-operator \
  --namespace neo4j-operator-system

Avoid --reuse-values as a blanket habit. It pins every value from the previous release and discards any new chart defaults introduced by the upgrade. Prefer re-passing your overrides explicitly (-f my-values.yaml or --set ...) so new defaults take effect.

Uninstall: see Uninstalling the Operator — Neo4j custom resources must be deleted before the operator is removed, or their finalizers will deadlock.

Method 2: OCI Registry

Helm 3.8 or later is required for OCI support.

helm install neo4j-operator oci://ghcr.io/priyolahiri/charts/neo4j-operator \
  --version 1.21.1 \
  --namespace neo4j-operator-system \
  --create-namespace

Use the chart version without the v prefix (for example, 1.21.1).

Method 3: Quick Install from GitHub Release

For environments where running helm is inconvenient, every release also publishes a single kubectl-applyable YAML bundle.

These are GitHub Release assets, not files in the repository. You won't find neo4j-kubernetes-operator-complete.yaml by browsing the source tree on the Code tab — it's attached to each tagged release under Releases → Assets, and built fresh by the release pipeline. Download it from the releases/download/<tag>/ URL below (substitute a real <tag> for ${RELEASE_VERSION}).

RELEASE_VERSION=v1.21.1  # Replace with desired version

kubectl apply --server-side -f https://github.com/priyolahiri/neo4j-kubernetes-operator/releases/download/${RELEASE_VERSION}/neo4j-kubernetes-operator-complete.yaml

Use --server-side: the largest CRDs in the bundle exceed the 256 KiB last-applied-configuration annotation limit that client-side kubectl apply relies on, so server-side apply is the recommended form.

To always pull the latest published release, use the latest/download/ path — GitHub redirects it to the newest release's asset, so no tag lookup and no gh CLI are needed:

kubectl apply --server-side -f https://github.com/priyolahiri/neo4j-kubernetes-operator/releases/latest/download/neo4j-kubernetes-operator-complete.yaml

Pin an explicit tag for anything reproducible — a GitOps repo, a runbook, or a cluster you need to be able to rebuild identically. latest is for a first look, and it moves under you.

What this installs:

  • Custom Resource Definitions (CRDs)
  • Operator Deployment (multi-arch images from ghcr.io)
  • All required RBAC permissions (ClusterRole, ClusterRoleBinding, ServiceAccount)
  • Deployed to the neo4j-operator-system namespace

To find available releases:

# Visit: https://github.com/priyolahiri/neo4j-kubernetes-operator/releases
# Or use the GitHub CLI:
gh release list --repo priyolahiri/neo4j-kubernetes-operator

CRDs only (manage the operator deployment separately):

kubectl apply --server-side -f https://github.com/priyolahiri/neo4j-kubernetes-operator/releases/download/${RELEASE_VERSION}/neo4j-kubernetes-operator.yaml

Supported architectures: linux/amd64, linux/arm64

Upgrade:

Upgrading a Method 3 install is just applying the new release's complete bundle (server-side). It includes the updated CRDs, and the operator Deployment rolls to the new image automatically:

RELEASE_VERSION=v1.21.1  # The version you are upgrading to

kubectl apply --server-side -f https://github.com/priyolahiri/neo4j-kubernetes-operator/releases/download/${RELEASE_VERSION}/neo4j-kubernetes-operator-complete.yaml

Method 4: Helm from Cloned Repository

Useful for testing an unreleased commit or customising the chart locally.

git clone https://github.com/priyolahiri/neo4j-kubernetes-operator.git
cd neo4j-kubernetes-operator

LATEST_TAG=$(git describe --tags --abbrev=0)
git checkout $LATEST_TAG    # or omit to test main

helm install neo4j-operator ./charts/neo4j-operator \
  --namespace neo4j-operator-system \
  --create-namespace \
  --set image.tag="${LATEST_TAG#v}"   # e.g. 1.21.1 — see note below

You must set image.tag for a from-clone install. In the repository, Chart.yaml's version/appVersion are a 0.0.1 placeholder that the release pipeline stamps from the git tag at publish time. Without --set image.tag, the chart resolves the image to ghcr.io/priyolahiri/neo4j-kubernetes-operator:0.0.1, which doesn't exist (ImagePullBackOff). Set it to a released version (the LATEST_TAG above with the v stripped), or build and load a local image and point image.repository / image.tag at it — see the developer guide. (Installing the published chart via Methods 1–2 doesn't need this — the released chart already carries the real version.)

Method 5: Git Clone with Make Targets

For development, customization, or when you need to build from source:

# Clone the repository
git clone https://github.com/priyolahiri/neo4j-kubernetes-operator.git
cd neo4j-kubernetes-operator

# Checkout the latest release tag
LATEST_TAG=$(git describe --tags --abbrev=0)
git checkout $LATEST_TAG

# Install CRDs and operator
make install      # Install CRDs into your cluster
make deploy-prod  # Deploy operator (builds and uses local image)

# Alternative deployment options:
make deploy-prod-local     # Explicit local build and deploy to Kind cluster
make deploy-prod-registry  # Deploy from ghcr.io registry (requires authentication)

What this installs:

  • Custom Resource Definitions (CRDs)
  • Operator Deployment
  • All required RBAC permissions
  • ServiceAccount and ClusterRole bindings

Custom Kustomize Configuration

For deployments that need a custom namespace, labels, or image tag, layer your own kustomization on top of config/default:

# kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- config/default
namespace: my-neo4j-operator
commonLabels:
  team: database
images:
- name: ghcr.io/priyolahiri/neo4j-kubernetes-operator
  newTag: v1.21.1
kubectl apply -k .

Also patch OPERATOR_VERSION

config/default carries the placeholder OPERATOR_VERSION: latest, which only the release workflow overwrites. Pinning newTag above does not change it, so tooling that reads the Deployment — such as kubectl neo4j validate's version-skew warning — falls back to reading your image tag. That works, but setting both is clearer, and is required if you pull the image by digest:

patches:
- target:
    kind: Deployment
    labelSelector: control-plane=controller-manager
  patch: |
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: controller-manager
    spec:
      template:
        spec:
          containers:
          - name: manager
            env:
            - name: OPERATOR_VERSION
              value: v1.21.1

This is a strategic merge patch, matched on the container and variable names. A positional JSON patch (/env/0/value) would silently rewrite the wrong variable if the base ever reorders its env list.

The operator's own neo4j_operator_build_info metric is unaffected either way — it reads the version compiled into the released binary, not the environment. See Monitoring.

For local development (Kind cluster + local image build) use make dev-cluster followed by make operator-setup. See the developer guide for the full inner-loop workflow.

Verifying Image Signatures

All release images are multi-arch and signed with Sigstore Cosign keyless signing — signatures are recorded in the public Rekor transparency log. Security teams can verify any release tag before admitting it:

cosign verify ghcr.io/priyolahiri/neo4j-kubernetes-operator:<tag> \
  --certificate-identity-regexp='github.com/priyolahiri' \
  --certificate-oidc-issuer='https://token.actions.githubusercontent.com'

For the full list of quality gates every release passes (integration matrices, the blocking install/upgrade/uninstall gate), see Supported Neo4j Versions → Release quality.

Verifying the Installation

After installation, verify that the operator is running:

# Check operator pod status (default namespace: neo4j-operator-system)
kubectl get pods -n neo4j-operator-system

# Check CRDs are installed
kubectl get crd | grep neo4j

# View operator logs
kubectl logs -n neo4j-operator-system -l control-plane=controller-manager --tail=-1

Expected output:

# Pod should be Running (single manager container — 1/1).
# Helm installs (Methods 1, 2, 4) name the pod neo4j-operator-xxx;
# kubectl-apply / make installs (Methods 3, 5) name it neo4j-operator-controller-manager-xxx.
NAME                              READY   STATUS    RESTARTS   AGE
neo4j-operator-xxx                1/1     Running   0          1m

# CRDs should be present (27 total)
auracustomermanagedkeys.neo4j.neo4j.com
auradatabasebackups.neo4j.neo4j.com
auradatabaserestores.neo4j.neo4j.com
auradatabases.neo4j.neo4j.com
aurainstances.neo4j.neo4j.com
aurainvites.neo4j.neo4j.com
auraipfilters.neo4j.neo4j.com
auraorganizationmembers.neo4j.neo4j.com
auraprojectmembers.neo4j.neo4j.com
auraproviderconfigs.neo4j.neo4j.com
aurarestores.neo4j.neo4j.com
aurasnapshots.neo4j.neo4j.com
neo4jauthrules.neo4j.neo4j.com
neo4jbackups.neo4j.neo4j.com
neo4jcompositedatabases.neo4j.neo4j.com
neo4jdatabasealiases.neo4j.neo4j.com
neo4jdatabases.neo4j.neo4j.com
neo4jenterpriseclusters.neo4j.neo4j.com
neo4jenterprisestandalones.neo4j.neo4j.com
neo4jplugins.neo4j.neo4j.com
neo4jreplicadatabases.neo4j.neo4j.com
neo4jreplicapromotions.neo4j.neo4j.com
neo4jrestores.neo4j.neo4j.com
neo4jrolebindings.neo4j.neo4j.com
neo4jroles.neo4j.neo4j.com
neo4jshardeddatabases.neo4j.neo4j.com
neo4jusers.neo4j.neo4j.com

Available Make Targets

After cloning the repository, you have access to these make targets:

Installation & Deployment

Target Description
make install Install CRDs into your cluster
make deploy-prod Deploy operator with production configuration
make deploy-dev Deploy with development configuration
make undeploy-prod/undeploy-dev Remove the operator Deployment + RBAC only (keeps CRDs and namespace)
make uninstall Remove CRDs — separate, destructive step (also removes all Neo4j instances)

Development & Testing

Target Description
make dev-cluster Create Kind development cluster
make operator-setup Deploy operator in-cluster to an existing Kind cluster (required for proper DNS)
make test-unit Run unit tests
make test-integration Run integration tests

Code Quality

Target Description
make fmt Format code with gofmt
make lint Run golangci-lint (strict mode)
make lint-lenient Run golangci-lint with relaxed rules
make vet Run go vet
make security Run gosec security scan
make tidy Tidy and verify go modules
make clean Clean build artifacts
make test-coverage Generate test coverage report

Getting Started with Examples

After installing the operator, you can deploy your first Neo4j instance using examples.

Option 1: Apply an Example Directly from GitHub

If you haven't cloned the repository, apply an example straight from the raw file URL at a release tag (no download or extract step needed):

RELEASE_VERSION=v1.21.1  # Replace with your installed version

# Create admin secret (required for Neo4j authentication)
kubectl create secret generic neo4j-admin-secret \
  --from-literal=username=neo4j \
  --from-literal=password=your-secure-password

# Deploy your first Neo4j instance
kubectl apply -f https://raw.githubusercontent.com/priyolahiri/neo4j-kubernetes-operator/${RELEASE_VERSION}/examples/standalone/single-node-standalone.yaml

# Check deployment status
kubectl get neo4jenterprisestandalone
kubectl get pods

# Access Neo4j Browser
kubectl port-forward svc/standalone-neo4j-client 7474:7474 7687:7687

Browse all available examples at https://github.com/priyolahiri/neo4j-kubernetes-operator/tree/main/examples.

Option 2: Using Examples from Cloned Repository

If you cloned the repository, examples are available in the local examples/ directory:

# Create admin secret (required for Neo4j authentication)
kubectl create secret generic neo4j-admin-secret \
  --from-literal=username=neo4j \
  --from-literal=password=your-secure-password

# Deploy your first Neo4j instance
kubectl apply -f examples/standalone/single-node-standalone.yaml

# Check deployment status
kubectl get neo4jenterprisestandalone
kubectl get pods

# Access Neo4j Browser
kubectl port-forward svc/standalone-neo4j-client 7474:7474 7687:7687

Uninstalling the Operator

The order below matters and is the same for every install method. Neo4j custom resources carry finalizers that only the running operator can remove — if you delete the operator (or the CRDs) first, every remaining CR wedges in Terminating forever.

Warning: Deleting CRDs cascades to every Neo4j instance the operator manages. Back up first.

Step 1 — Delete all Neo4j custom resources and wait for them to disappear (the operator must still be running to strip their finalizers):

# List everything the operator manages — this must come back EMPTY before step 2
kubectl get neo4jenterpriseclusters,neo4jenterprisestandalones,neo4jdatabases,neo4jshardeddatabases,neo4jcompositedatabases,neo4jdatabasealiases,neo4jreplicadatabases,neo4jreplicapromotions,neo4jbackups,neo4jrestores,neo4jusers,neo4jroles,neo4jrolebindings,neo4jauthrules,neo4jplugins -A
# Also any Aura resources you created (they hold finalizers too; check each CR's
# deletionPolicy before deleting — Delete removes the resource in Aura as well):
kubectl get auraproviderconfigs,aurainstances,aurasnapshots,aurarestores,auracustomermanagedkeys,auraipfilters,auradatabases,auradatabasebackups,auradatabaserestores,auraorganizationmembers,auraprojectmembers,aurainvites -A

# Delete them (repeat per namespace, or script over the list above), e.g.:
kubectl delete neo4jenterpriseclusters,neo4jenterprisestandalones,neo4jdatabases,neo4jshardeddatabases,neo4jcompositedatabases,neo4jdatabasealiases,neo4jreplicadatabases,neo4jreplicapromotions,neo4jbackups,neo4jrestores,neo4jusers,neo4jroles,neo4jrolebindings,neo4jauthrules,neo4jplugins --all -n <namespace>

# Re-run the `kubectl get ... -A` above and wait until no resources are listed

Step 2 — Remove the operator (only once step 1 shows no remaining CRs):

# Helm install — Methods 1, 2, 4
helm uninstall neo4j-operator --namespace neo4j-operator-system

# kubectl-apply install — Method 3: delete the Deployment + RBAC, NOT the bundle file (see warning below)
kubectl delete deployment neo4j-operator-controller-manager -n neo4j-operator-system
kubectl get clusterrolebinding,clusterrole -o name | grep neo4j-operator | xargs kubectl delete

# git-clone + make install — Method 5 (removes Deployment + RBAC, keeps CRDs and namespace)
make undeploy-prod   # or undeploy-dev

Do not run kubectl delete -f neo4j-kubernetes-operator-complete.yaml. It deletes the CRDs and namespace in file order while CRs may still exist, which wedges every live CR in Terminating (their finalizers can no longer be removed once the operator is gone). Use the explicit steps above instead.

Step 3 — Optionally delete the CRDs and namespace. This destroys the definitions of all remaining Neo4j custom resources — skip it if you plan to reinstall:

kubectl get crd -o name | grep neo4j.neo4j.com | xargs kubectl delete

kubectl delete namespace neo4j-operator-system

Escape hatch — a CR is already stuck in Terminating (operator removed before the CR was deleted). Strip its finalizers manually:

kubectl patch <kind>/<name> -n <namespace> -p '{"metadata":{"finalizers":[]}}' --type=merge

Caveat: removing finalizers skips the operator's cleanup logic — data the finalizer would have cleaned up (databases, backup artifacts, child resources) may be orphaned.

Troubleshooting Installation

Common Issues

1. CRDs Not Installing

# Check if CRDs exist
kubectl get crd | grep neo4j

# If missing, install CRDs manually
make install

2. Operator Pod Not Starting

# Check operator logs
kubectl logs -n neo4j-operator-system -l control-plane=controller-manager --tail=-1

# Check operator pod events
kubectl describe pod -n neo4j-operator-system -l control-plane=controller-manager

3. RBAC Permission Issues

# Check if ServiceAccount exists
kubectl get sa -n neo4j-operator-system

# Check ClusterRole and ClusterRoleBinding
kubectl get clusterrole | grep neo4j-operator
kubectl get clusterrolebinding | grep neo4j-operator

4. Image Pull Errors

If the operator pod shows ImagePullBackOff or ErrImagePull:

# Check pod events for image pull errors
kubectl describe pod -n neo4j-operator-system -l control-plane=controller-manager

# Verify image exists and is accessible
docker pull ghcr.io/priyolahiri/neo4j-kubernetes-operator:latest

# For private registries, create image pull secret
kubectl create secret docker-registry ghcr-secret \
  --docker-server=ghcr.io \
  --docker-username=<github-username> \
  --docker-password=<github-token> \
  --namespace=neo4j-operator-system

# Add imagePullSecrets to the deployment (named neo4j-operator-controller-manager for
# Methods 3/5; for a Helm install it is `neo4j-operator`, or set `imagePullSecrets` in the chart values)
kubectl patch deployment neo4j-operator-controller-manager \
  -n neo4j-operator-system \
  --type='json' \
  -p='[{"op": "add", "path": "/spec/template/spec/imagePullSecrets", "value": [{"name": "ghcr-secret"}]}]'

Note: The ghcr.io images are public for this operator, so authentication should not be required for pulling images.

5. Manifest URL Not Found (404 Error)

If you get a 404 error when applying manifests from GitHub releases:

# Verify the release exists
gh release list --repo priyolahiri/neo4j-kubernetes-operator

# Or visit: https://github.com/priyolahiri/neo4j-kubernetes-operator/releases

# Ensure you're using the correct version format (with 'v' prefix)
# Correct: v1.21.1
# Incorrect: 1.21.1

Installation Requirements

  • Kubernetes: Version 1.32 or higher
  • Neo4j: Version 5.26 LTS (the final SemVer release) or any CalVer release (2025.x, 2026.x, and onward)
  • cert-manager: Version 1.20+ (optional, only required for Neo4j deployments that use cert-manager TLS, i.e. spec.tls.mode: cert-manager)
  • Permissions: Cluster-admin access for CRD and RBAC installation

cert-manager install order: The operator installs and runs fine without cert-manager. It only watches cert-manager Certificate resources when the cert-manager CRDs are present at operator startup. If you install cert-manager after the operator, restart the operator so the watch becomes active:

kubectl rollout restart deployment -n neo4j-operator-system -l app.kubernetes.io/name=neo4j-operator
If you know you'll use cert-manager TLS, install cert-manager before the operator to avoid the restart.

OpenShift note: Clusters enforcing SCCs with allocated UID/FSGroup ranges should disable the chart’s pod security context so SCC can inject IDs, then bind an appropriate SCC (e.g., restricted) to the operator service account:

helm install neo4j-operator ./charts/neo4j-operator \
  --namespace neo4j-operator-system \
  --create-namespace \
  --set podSecurityContextEnabled=false

oc adm policy add-scc-to-user restricted -z neo4j-operator -n neo4j-operator-system

Installing via OLM on OpenShift

  • Build/push a bundle and catalog (see docs/developer_guide/openshift_olm.md).
  • Apply the sample CatalogSource and Subscription under config/samples/olm/ after updating the catalog image and channel to match your bundle.

Next Steps

Once installed, see: