The keystone Helm chart (deploy/helm/keystone in the release) runs every Govern plane as its own workload. It supersedes the older ni-auth chart, which runs the server only.
Requirements
- Kubernetes 1.29 or later, and Helm 3
- PostgreSQL 15 or later reachable from the cluster (TLS required by default)
- The Govern images in a registry your cluster can pull from:
keystone-server,keystone-console,keystone-planeandkeystone-migrate, tagged with the release commit - For more than one server replica, a storage class that supports ReadWriteMany (for example NFS, CephFS, EFS, Azure Files or Filestore), or an S3-compatible bucket
What the chart deploys
| Plane | Workload | Binary | Port | Probes |
|---|---|---|---|---|
| Control | keystone-server | ni-auth | 8080 (API), 9090 (worker API) | readiness /readyz/selfguard, liveness /healthz |
| Console | keystone-console | Next.js | 3000 | /signin |
| Telemetry | keystone-ingest | keystone-ingest | 8081 | /readyz, /healthz |
| Data | keystone-bridge | keystone-bridge | 8082, 3129 | /readyz, /healthz |
| Compliance | keystone-comply | keystone-comply | 9093 | /readyz, /healthz |
Each plane gets its own ServiceAccount (no token mounted), Service, PodDisruptionBudget and CPU autoscaler; topology spread across zones (required) and hosts (preferred); a non-root security context with a read-only root file system and every capability dropped; rolling updates with maxUnavailable: 0; and NetworkPolicies that deny ingress by default.
Before every install and upgrade, hooks:
- create or update the three database roles (
databaseRoles.bootstrap, run as the database superuser or the Amazon RDS master user); - run the migrations as
keystone_ownerfrom thekeystone-migrateimage of the same release; - run
ni-auth cell init, which is idempotent.
The planes then connect as keystone_app (DATABASE_URL, under row-level security) and keystone_platform (DATABASE_PLATFORM_URL). Only the hooks mount the owner and superuser credentials. If your roles are managed outside the chart, set databaseRoles.bootstrap: false.
Create the Secrets
Secrets never go in values. Either create the Kubernetes Secrets named in secrets.* yourself, or turn on externalSecrets.enabled to sync them from AWS Secrets Manager with the External Secrets Operator.
| Value | Default name | Holds |
|---|---|---|
secrets.server | keystone-server-env | Every server key (KMS_MASTER_KEY, the NI_AUTH_* peppers and encryption keys, NI_OBSERVABILITY_SIGNING_KEY, and the rest listed in Deploy and operate) |
secrets.database | keystone-database | DATABASE_URL (keystone_app) and DATABASE_PLATFORM_URL (keystone_platform) |
secrets.databaseAdmin | keystone-database-admin | DATABASE_OWNER_URL (keystone_owner); with role bootstrap also KEYSTONE_SUPERUSER_URL and KEYSTONE_OWNER_PASSWORD, KEYSTONE_APP_PASSWORD, KEYSTONE_PLATFORM_PASSWORD. Hooks only. |
secrets.console | keystone-console-env | The console's settings |
secrets.ingest | keystone-ingest-env | NI_INGEST_SERVICE_TOKEN |
secrets.bridge | keystone-bridge-env | The data plane's settings |
secrets.comply | keystone-comply-env | The compliance plane's settings |
secrets.oidcKey | keystone-oidc | oidc_private_key (PEM) |
Set the values
| Value | Set to |
|---|---|
image.registry, image.tag | Your registry and the release commit. The tag is required and the chart refuses latest. |
publicApiUrl | The public API URL, https://api.<your domain>. It must be HTTPS. |
cell.* | cell.id stays local for a self-hosted installation. |
database.host, database.name, database.sslmode | Your PostgreSQL server; sslmode defaults to require. |
database.poolPerPod | Connections per server pod (default 50). Keep pods times this below PostgreSQL's max_connections. |
ingress.enabled, ingress.className, ingress.hosts, ingress.tls | Your Ingress. hosts has console, api, auth and terminal. |
networkPolicy.allowedCidrs | The networks your load balancers reach the pods from (default 10.0.0.0/8). |
The console host is the address people and clients are given, so the Ingress sends its /.well-known/* and /oauth2/* paths to the server and the rest to the console. A load balancer in front of your Ingress must pass the Host header through unchanged.
Object storage
Exports and artifacts (audit and Observe exports, agent release binaries) live in shared object storage, so any server replica can serve them. Set exactly one; the chart refuses to render without a store.
- A shared directory (the default, and the choice for air-gapped installations): a ReadWriteMany volume every server replica mounts. Use
objectStorage.local.existingClaim, or let the chart create one withobjectStorage.local.create(100Gi by default, kept on uninstall). ReadWriteOnce works only with a single server replica. - S3 or an S3-compatible store:
objectStorage.s3.bucket,kmsKeyId(required),prefix(defaultkeystone/), andendpointfor MinIO, Ceph or another S3-compatible store, withobjectStorage.local.create.enabled=false. Never make the bucket public: downloads are streamed through the API, which checks access and audits each one.
Replicas
By default the server runs 3 replicas (autoscaling 3 to 9, at least 2 available), and the console, ingest, bridge and compliance planes 2 each. Adjust server.replicas, server.hpa and server.pdb and the matching values of the other planes.
Render and install
Render the chart and review it before you install:
helm template keystone deploy/helm/keystone --kube-version 1.31.0 \
--set image.tag=<sha> --set publicApiUrl=https://api.example.com
Then install or upgrade with your values file:
helm upgrade --install keystone deploy/helm/keystone -f values.yaml
After the install, kubectl get pods,statefulsets,pvc,hpa,pdb in the release namespace shows the planes.
Optional components
These are off by default; with them off, the chart renders exactly as without them.
| Value | Turns on |
|---|---|
telemetryPipeline.enabled | The built-in telemetry pipeline: the bridge becomes a StatefulSet with a write-ahead log on its own volume, and ingest writes hourly PostgreSQL partitions and compacts them to Parquet. Needs exactly one cold store: an S3 bucket or a ReadWriteMany directory. |
streams.enabled | The Streams brokers as a StatefulSet, coordinated through the main database. |
dns.enabled | The DNS name servers. |
Each needs its own Secret first; the chart README in the release lists the keys.
Connection pooling
With many replicas, use PgBouncer in session mode and point both DATABASE_URL and DATABASE_PLATFORM_URL at it. Transaction mode is not supported, because the tenant of each connection is a session setting.
Rollback and uninstall
helm history keystone
helm rollback keystone
Database migrations are not rolled back by Helm. Take a backup before every upgrade and restore it rather than migrating down.
helm uninstall keystone
Volumes the chart created for object storage are kept on uninstall; delete them yourself if you no longer need the data.