Kubernetes HA setup with Helm
Summary: Step-by-step procedure for bringing up a 3-node OpenBao HA cluster on Kubernetes using the official Helm chart with integrated Raft storage. Covers the minimum install, TLS, auto-unseal, and what to add for production.
Sources: raw/docs/platform/k8s/helm/examples/ha-with-raft.md, raw/docs/platform/k8s/helm/examples/ha-tls.md, raw/docs/platform/k8s/helm/configuration.md, raw/docs/platform/k8s/helm/run.md, raw/docs/platform/k8s/helm/terraform.md, raw/docs/platform/k8s/helm/examples/snapshot-cronjob.md.
Last updated: 2026-05-19
Prerequisites
- Helm 3.6+. The chart is not compatible with Helm 2 (source:
raw/docs/platform/k8s/helm/examples/ha-with-raft.md). - A Kubernetes cluster with persistent-volume support (Raft needs durable storage per pod).
If you do not yet have a Kubernetes cluster, see k8s-ha-from-scratch — that page covers the full path from three bare Ubuntu 24.04 servers (kubeadm + Cilium + cert-manager + this Helm chart) and lands at the same operational state as this page. This page assumes the cluster, Helm and a default StorageClass already exist.
1 — Add the OpenBao Helm chart repository
The chart is published at https://openbao.github.io/openbao-helm (source: raw/docs/platform/k8s/helm/terraform.md). Register it locally and confirm the chart is reachable:
helm repo add openbao https://openbao.github.io/openbao-helm
helm repo update
helm search repo openbao/openbao
helm search repo openbao/openbao should list the chart and its current version (source: raw/docs/platform/k8s/helm/run.md). If you need to pin a specific chart version later, pass --version <x.y.z> to helm install/helm upgrade.
A few alternatives if helm repo add doesn’t fit your workflow:
- Terraform — use the
helm_releaseresource withrepository = "https://openbao.github.io/openbao-helm"andchart = "openbao". See Terraform below andraw/docs/platform/k8s/helm/terraform.md. - Local checkout — clone
github.com/openbao/openbao-helmand install from the path:helm install openbao ./charts/openbao.
2 — Install the chart in HA + Raft mode
helm install openbao openbao/openbao \
--set='server.ha.enabled=true' \
--set='server.ha.raft.enabled=true'
(source: raw/docs/platform/k8s/helm/examples/ha-with-raft.md)
By default this brings up three pods — openbao-0, openbao-1, openbao-2 — all in sealed state. The chart provisions a headless internal service (openbao-internal) for node-to-node traffic plus a regular service for client access.
3 — Initialize and unseal the first pod
kubectl exec -ti openbao-0 -- bao operator init
kubectl exec -ti openbao-0 -- bao operator unseal
Save the unseal-key shards and the initial root token. With a Shamir seal you need a threshold of shards on every node every time it restarts — partial unseal does not propagate across the cluster. This is why production deployments switch to auto-unseal (see below).
4 — Join and unseal the followers
Each follower joins the cluster via the headless service, then is unsealed independently:
kubectl exec -ti openbao-1 -- bao operator raft join http://openbao-0.openbao-internal:8200
kubectl exec -ti openbao-1 -- bao operator unseal
kubectl exec -ti openbao-2 -- bao operator raft join http://openbao-0.openbao-internal:8200
kubectl exec -ti openbao-2 -- bao operator unseal
(source: raw/docs/platform/k8s/helm/examples/ha-with-raft.md)
5 — Verify
kubectl exec -ti openbao-0 -- bao login
kubectl exec -ti openbao-0 -- bao operator raft list-peers
Expected: openbao-0 as leader, the others as follower, all Voter: true. Example output (source: raw/docs/platform/k8s/helm/examples/ha-with-raft.md):
Node Address State Voter
---- ------- ----- -----
a1799962-... openbao-0.openbao-internal:8201 leader true
e6876c97-... openbao-1.openbao-internal:8201 follower true
4b5d7383-... openbao-2.openbao-internal:8201 follower true
Production hardening
The minimum install above is functional but not production-ready. Add:
TLS
Without TLS configuration, Raft join logs x509: certificate is valid for ${SERVICE}, … not openbao-${N}.${SERVICE} because the per-pod DNS name doesn’t match the cert’s CN (source: raw/docs/platform/k8s/helm/examples/ha-tls.md).
Two supported fixes:
-
Solution 1 — auto-join with
leader_tls_servername. Oneretry_joinper pod, each pinningleader_tls_servernameto the cert CN:storage "raft" { path = "/openbao/data" retry_join { leader_api_addr = "https://openbao-0.${SERVICE}:8200" leader_tls_servername = "${CN}" leader_client_cert_file = "/openbao/tls/openbao.crt" leader_client_key_file = "/openbao/tls/openbao.key" leader_ca_cert_file = "/openbao/tls/openbao.ca" } # ...one retry_join per node } -
Solution 2 — front with a load balancer. A single
retry_joinagainst the LB’s stable name:retry_join { leader_api_addr = "https://openbao-active:8200" leader_client_cert_file = "/openbao/tls/openbao.crt" leader_client_key_file = "/openbao/tls/openbao.key" leader_ca_cert_file = "/openbao/tls/openbao.ca" }
Cert provisioning examples (cert-manager and manual) live in raw/docs/platform/k8s/helm/examples/standalone-tls.md and raw/docs/platform/k8s/helm/examples/injector-tls-cert-manager.md.
Auto-unseal
Manual Shamir unseal of every pod after every restart is operationally painful in Kubernetes. Switch to auto-unseal via a seal stanza:
- Cloud KMS —
seal "awskms" { … },seal "gcpckms" { … },seal "azurekeyvault" { … },seal "ocikms" { … },seal "alicloudkms" { … }. - Another OpenBao cluster —
seal "transit" { … }. - PKCS#11 HSM —
seal "pkcs11" { … }.
Full backend list and config at raw/docs/configuration/seal/. Conceptual overview in seal-unseal. Auto-unseal also defines a separate recovery key (Shamir-split) used for operator generate-root-style operations.
Snapshots
Schedule periodic Raft snapshots with the CronJob in raw/docs/platform/k8s/helm/examples/snapshot-cronjob.md. Snapshots are the supported backup primitive for the integrated storage backend — see storage and upgrading for restore considerations.
Audit
The cluster has no audit device until you enable one. Always enable at least two (different sinks) — if the sole audit device blocks, OpenBao stops serving requests.
Values customization
The Helm values surface is documented at raw/docs/platform/k8s/helm/configuration.md and raw/docs/platform/k8s/helm/run.md. Common knobs:
server.ha.replicas— number of pods (3 or 5 typical for Raft).server.dataStorage/server.auditStorage— PVC sizes and storage classes.server.resources— requests/limits.server.affinity— pod anti-affinity to spread across nodes/zones (essential for real HA).server.extraEnvironmentVarsandserver.extraVolumes— for KMS credentials, TLS certs.injector.enabled— toggle the Agent Injector mutating webhook.
OpenShift
OpenShift has its own SCC/permissions tweaks — see raw/docs/platform/k8s/helm/openshift.md.
Terraform
If you’d rather provision via Terraform than direct Helm, see raw/docs/platform/k8s/helm/terraform.md.
Related pages
- kubernetes-platform — overview of the K8s integration patterns
- high-availability — leader/standby mechanics, request forwarding
- seal-unseal — Shamir vs auto-unseal trade-offs
- storage — Raft details
- configuration —
storage,seal,listenerstanzas referenced above - upgrading — HA upgrade ordering for an existing cluster