Docs
HelmGuides

High Availability

Run NetBox Enterprise in a highly available configuration on a Helm install, using in-cluster object storage and a multi-instance PostgreSQL and Redis tier. Covers enablement, node requirements, how to verify the result, and the checks Helm does not perform for you.

Available in NetBox Enterprise 2.3.0

In-cluster object storage and the hardened in-cluster data tier are new in 2.3.0. On earlier releases, a highly available Helm deployment requires external PostgreSQL, external Redis, and external S3-compatible storage.

A highly available NetBox Enterprise deployment needs three tiers to survive the loss of a node: object storage for NetBox media, the PostgreSQL database, and the Redis cache. From 2.3.0, all three can run inside the cluster on a Helm install, with the operator provisioning and managing them.

Read this first: you own the correctness check

On Helm, Ready=True does not mean "highly available"

On Embedded Cluster, install-time preflight checks and a Ready gate refuse a deployment that claims to be highly available but cannot survive node loss. Those checks do not exist on a Helm install. The operator will not stop you from building a data tier that looks HA and is not, and the NetBoxEnterprise resource will report Ready=True while it does.

The specific gap: a single-instance PostgreSQL or Redis tier alongside an otherwise-HA deployment produces no preflight failure, no warning condition, and no effect on Ready. Verify your replica counts yourself — see Verify the result.

This is the one operational difference that matters between the two install paths. Everything else below behaves the same way.

Requirements

  1. Three or more nodes. This is a hard floor, not a recommendation — see Node requirements.
  2. Kubernetes 1.33 or later, the chart's tested floor.
  3. The multi-node entitlement (included in the Premium tier), the same entitlement Embedded Cluster requires for an HA data tier.
  4. A conforming default StorageClass. Every NetBox Enterprise volume must be backed by a class that provisions local or CSI PersistentVolumes, never hostPath. This matters more than it sounds: Velero's filesystem backup silently skips hostPath volumes, producing a Completed backup containing no volume data at all. The local-path-provisioner default shipped by k3s, k3d, and kind does not conform. See Backups.
  5. The three CRD charts installed at the matching version, as in Standard Installation.

Enable high availability

Everything is configured through netboxEnterprise.spec, which the chart passes through to the NetBoxEnterprise resource:

netboxEnterprise:
  enabled: true
  spec:
    # In-cluster object storage (Garage). Note the value is kebab-case.
    storageBackend: in-cluster

    # Multi-instance data tier.
    postgresql:
      external: false
      instances: 3
    redis:
      external: false
      clusterSize: 3

    # Application tier.
    netbox:
      replicas: 2
      worker:
        replicas: 2

    # Spread NetBox pods across nodes. Leave labelSelector empty — the
    # operator injects the per-deployment selector labels.
    replication:
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: ScheduleAnyway
          labelSelector: {}

Apply it with helm upgrade --install as usual.

The storage backend value

storageBackend accepts three values, and they are kebab-case:

ValueMeaning
pvcNode-local volume (the default). Not HA-capable.
externalAn external S3-compatible endpoint you provide. See Object Storage.
in-clusterThe operator-provisioned in-cluster Garage object store.

InCluster and incluster are rejected by the resource's schema.

Choosing the data-tier values

postgresql.instances and redis.clusterSize both default to 0, which means auto-scale to min(node_count, 3). On a three-node cluster the default therefore already gives you a three-instance PostgreSQL and a three-node Redis.

Set them explicitly to 3 if you want the requested count recorded in your values rather than inferred from the node count at reconcile time. Either approach is supported. What is not HA:

  • instances: 1 or clusterSize: 1 — a single instance cannot survive losing its node, and nothing will warn you.
  • Any value above 1 on a cluster with fewer than three nodes, where the replicas have nowhere to spread to.

What the operator creates

With storageBackend: in-cluster, and without further configuration, the operator:

  • provisions a three-node Garage cluster at replication factor 3, so every object is held on all three nodes and the store survives the loss of any one;
  • creates the netbox bucket and an access key, and points NetBox at the in-cluster S3 endpoint;
  • waits for Garage to apply its storage layout and pass an S3 read/write round-trip before NetBox starts serving, so the application never comes up against storage that cannot hold media.

Garage is not sized from the NetBoxEnterprise resource — there is no garage section to configure. It uses one pod per node at 500m CPU and 512Mi memory, with a 21Gi volume each (1Gi metadata + 20Gi data).

With object storage in use, NetBox media and scripts live in Garage rather than on a shared volume, so the ReadWriteMany storage requirement that otherwise applies to multi-replica NetBox does not apply here. Leaving netbox.storageAccessMode at its ReadWriteOnce default is correct.

Node requirements

Three nodes is a floor for a specific reason: Garage places one pod per node, enforced. Replication factor 3 across three pods only provides redundancy if those pods are on three different failure domains, so the constraint is hard rather than best-effort. On a cluster with fewer than three nodes, the pods cannot all be placed and the store cannot reach its intended redundancy.

The operator surfaces this. With storageBackend: in-cluster on a cluster below three nodes, the resource carries an InClusterStorageBelowNodeGate condition whose message names the node count it found and the ways out. Unlike the data-tier gap described above, this check does apply on Helm — it is derived from the live node count rather than from any Embedded Cluster signal.

It is advisory: it does not gate Ready. Treat it as an error anyway.

Size nodes for the added tiers on top of your normal NetBox Enterprise profile. Garage adds roughly half a core and 512Mi per node; the in-cluster data tier adds three PostgreSQL instances plus three Redis data pods and three Sentinel pods. Built-in PostgreSQL ships with no CPU or memory requests set — set postgresql.limits to bound it.

Verify the result

Because Ready=True is not sufficient evidence of high availability on Helm, check the tiers directly.

# 1. The resource is Ready.
kubectl -n netbox-enterprise get netboxenterprise netbox \
  -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}{"\n"}'

# 2. Garage is at three replicas, and its pods are on three DISTINCT nodes.
kubectl -n netbox-enterprise get garagecluster
kubectl -n netbox-enterprise get pods -o custom-columns='POD:.metadata.name,NODE:.spec.nodeName' \
  | grep garage

# 3. PostgreSQL has three instances, one master and two replicas.
kubectl -n netbox-enterprise get pods \
  -L postgres-operator.crunchydata.com/role \
  -l postgres-operator.crunchydata.com/cluster=netbox-postgres

# 4. Redis has three data pods and three sentinels, and reports a master.
kubectl -n netbox-enterprise get redisreplication

Step 2 is the one people skip. Three Garage pods on two nodes is not a redundant object store, and nothing in Ready reflects the difference.

Also check for warning conditions, which are present only when they apply:

kubectl -n netbox-enterprise get netboxenterprise netbox \
  -o jsonpath='{range .status.conditions[*]}{.type}={.status}{"\n"}{end}'

A fully healthy in-cluster HA deployment on three nodes carries none of InClusterStorageBelowNodeGate, SharedStorageNotMultiNode, or MultiNodeNotEntitled. See Status Conditions for the full list.

Failover behavior

Each tier fails over independently, without operator intervention:

  • PostgreSQL — losing the primary promotes a replica. The former primary rejoins as a replica when it returns.
  • Redis — Sentinel promotes a replica to master and the operator updates the RedisReplication resource to name it.
  • Garage — a surviving node continues serving media, because every object is held on all three.

Promotion is a matter of seconds for a pod-level failure. Node-level failure is slower, bounded by how quickly Kubernetes declares the node lost.

One consequence of node-local storage: an instance whose node is gone cannot restart elsewhere with its data, because its volume is tied to that node. Failover to a surviving replica is unaffected — that is the point of running three — but the failed instance stays down until its node returns or you replace it. Plan replacement capacity accordingly.

Backup and disaster recovery

In-cluster storage keeps media available through node loss. It is not a backup.

In-cluster storage is not a backup

Replication protects against node loss, not against deletion, corruption, or the loss of multiple nodes. Backups must target an external object store, outside the cluster and independent of it.

On Helm you supply and operate Velero yourself; there is no bundled snapshot mechanism and no airgap image vendoring. The operator-side pieces do apply — the resources it creates carry the labels Velero's backup selection relies on, and a restore into in-cluster storage waits for Garage before bringing NetBox up. See Backups for installing Velero, label selection, and restore procedure.

This is where the conforming-StorageClass requirement earns its place: a hostPath class produces backups that complete successfully and contain none of your data.

Limitations

  • The correctness check is yours. No preflight checks and no Ready gate protect a non-HA data tier on Helm. This is the difference from Embedded Cluster.
  • Object storage alone is not high availability. Setting storageBackend: in-cluster makes the media tier redundant; the database and cache tiers are configured separately.
  • Switching the backend later does not migrate media. Changing away from in-cluster storage leaves the Garage store in place rather than deleting it. Plan a media migration if you move between backends.
  • In-cluster storage is not a single-node option. It is intended for clusters of three or more nodes.

Next steps

  • Status Conditions — every condition the resource reports, and which ones gate Ready
  • Backups — Velero installation, storage conformance, and restore
  • External Database — using external PostgreSQL instead of the in-cluster tier
  • Architecture — how the operator and its managed components fit together

On this page