Kubernetes (Helm)
TL;DR — Official Helm chart at
oci://ghcr.io/authplane/charts/authplane. Ships with an optional Bitnami PostgreSQL subchart, split ingresses (public OAuth on:9000+ internal admin on:9001), Vault Transit signing support, ServiceMonitor for Prometheus, OTEL wiring. Migrations run automatically on pod boot. HA-ready withautoscaling.enabled— Vault Transit recommended when running multi-replica to avoid the shared-PVC problem.
Prerequisites
- Kubernetes 1.26+
- Helm 3.8+
- A PostgreSQL instance — either the Bitnami subchart the chart ships (dev/testing) or your own external DB (production)
Quick Start
From the OCI registry (recommended)
helm install authplane oci://ghcr.io/authplane/charts/authplane \
--version 0.1.0 \
-f values-production.yaml
From source (development)
git clone https://github.com/authplane/authserver.git
cd authserver
# Update chart dependencies (downloads Bitnami PostgreSQL subchart)
helm dependency update charts/authplane
# Install with built-in PostgreSQL
helm install authplane charts/authplane \
--set config.server.issuer=https://auth.example.com \
--set postgresql.enabled=true \
--set postgresql.auth.password=changeme \
--set secrets.sessionSecret=$(openssl rand -hex 32) \
--set secrets.adminApiKey=$(openssl rand -hex 32)
Storage modes
PostgreSQL (production)
Recommended for production. Either the Bitnami subchart or your own external DB works; either way the chart auto-adds an init container that waits for PostgreSQL to be ready before starting AuthPlane.
Option A — Bitnami subchart (dev/testing convenience):
# values-dev.yaml
config:
server:
issuer: https://auth.example.com
storage:
driver: postgres
postgresql:
enabled: true
auth:
username: authplane
password: changeme
database: authplane
secrets:
sessionSecret: "generate-with-openssl-rand-hex-32"
adminApiKey: "generate-with-openssl-rand-hex-32"
helm install authplane charts/authplane -f values-dev.yaml
Option B — External PostgreSQL (production):
# values-production.yaml
config:
server:
issuer: https://auth.example.com
storage:
driver: postgres
externalDatabase:
host: postgres.database.svc
port: 5432
user: authplane
password: secret
database: authplane
sslmode: require
# Or use a pre-existing Secret with the full DSN:
# existingSecret: my-db-secret
# existingSecretKey: dsn
secrets:
existingSecret: authplane-secrets # pre-created with session-secret + admin-api-key
SQLite (dev / single-node)
SQLite mode requires persistent storage and is limited to a single replica.
# values-sqlite.yaml
replicaCount: 1
config:
server:
issuer: http://localhost:9000
storage:
driver: sqlite
persistence:
enabled: true
size: 1Gi
secrets:
sessionSecret: "generate-with-openssl-rand-hex-32"
adminApiKey: "generate-with-openssl-rand-hex-32"
Warning — SQLite doesn’t support multiple replicas. The chart’s
NOTES.txtwarns ifreplicaCount > 1whiledriver: sqlite.
Cache propagation window — with
driver: sqlite, changes made via the Admin API (new resource servers, scope updates, broker-provider config) take up to 30 seconds to become visible to/.well-known/oauth-authorization-serverand the token endpoint. SQLite has noLISTEN/NOTIFY. PostgreSQL propagates in milliseconds via PG NOTIFY. Usually only matters during initial bring-up.
Migrations
Migrations run automatically on pod startup. The AuthPlane binary embeds all migration SQL via go:embed and applies pending migrations before serving traffic. No separate Job needed. The chart’s init container waits for PostgreSQL connectivity; the main process handles the migrations themselves.
If you want to run migrations manually (e.g., during a large upgrade):
kubectl exec -it deploy/authplane -- /authserver migrate
OIDC Federation (Google, Okta, Entra)
Users see a “Continue with [Provider]” button on the login page. Full setup in Guides: Federate to your IdP; the chart-specific bits:
Google Workspace
config:
oidc:
enabled: true
issuer: https://accounts.google.com
client_id: "YOUR_GOOGLE_CLIENT_ID"
client_secret: "YOUR_GOOGLE_CLIENT_SECRET"
display_name: "Google Workspace"
scopes: [openid, email, profile]
include_groups_scope: false # Google doesn't support groups scope
Okta
config:
oidc:
enabled: true
issuer: https://your-org.okta.com
client_id: "YOUR_OKTA_CLIENT_ID"
client_secret: "YOUR_OKTA_CLIENT_SECRET"
display_name: "Okta"
scopes: [openid, email, profile]
include_groups_scope: true # Okta supports groups
Secrets management for OIDC client secrets
Don’t put client secrets in values.yaml. Two options:
Env-var injection — reference a pre-created Kubernetes Secret:
config:
oidc:
enabled: true
client_secret_ref: AUTHPLANE_OIDC_CLIENT_SECRET
extraEnv:
- name: AUTHPLANE_OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: oidc-credentials
key: client-secret
Full config as sealed Secret — for GitOps workflows:
existingConfigSecret: my-sealed-config
Vault Transit signing (HSM-grade)
AuthPlane supports HashiCorp Vault Transit for JWT signing — private keys never leave Vault. Recommended for multi-replica deployments (avoids the shared-PVC problem) and any compliance environment.
vault:
signing:
enabled: true
address: https://vault.vault.svc:8200
mount: transit
keyName: authserver-signing
auth:
method: approle
approle:
roleId: "..."
secretId: "..."
# Or a pre-existing Secret with keys:
# vault-token (token auth)
# vault-approle-role-id + vault-approle-secret-id (approle)
# existingSecret: vault-signing-creds
Setting vault.signing.enabled=true auto-sets AUTHPLANE_SIGNING_KEY_STORE=vault_transit and injects the connection env vars.
Vault auth patterns in Kubernetes
- AppRole (recommended) — inject
roleId/secretIdviaexistingSecretor External Secrets Operator. - Vault Agent Sidecar — Vault Agent annotations via
podAnnotations; mount injected secrets viaextraVolumes/extraVolumeMounts. - Vault CSI Provider — mount secrets as volumes via
extraVolumes.
The chart does NOT include a Vault subchart — Vault is infrastructure your organization already runs.
Full walkthrough in Operate: Vault Transit.
Ingress — split public + admin
The chart provides separate ingress resources for OAuth (:9000) and Admin (:9001). These are different security boundaries — the Admin API should have restricted access.
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: auth.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: auth-tls
hosts:
- auth.example.com
adminIngress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/whitelist-source-range: 10.0.0.0/8
hosts:
- host: auth-admin.internal.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: auth-admin-tls
hosts:
- auth-admin.internal.example.com
Without an admin ingress, port-forward for the UI:
kubectl port-forward svc/authplane 9001:9001
# Open http://localhost:9001/admin/ui/
Secrets management
The chart handles three categories of secrets.
1. Session secret + Admin API key
# Option A — Inline (dev only; NOT stable across helm upgrade)
secrets:
sessionSecret: "generate-with-openssl-rand-hex-32"
adminApiKey: "generate-with-openssl-rand-hex-32"
# Option B — Pre-existing Secret (production)
secrets:
existingSecret: authplane-secrets
# Must contain keys: session-secret, admin-api-key
Warning — auto-generated secrets change on every
helm upgrade. Always use explicit values orexistingSecretfor production; regenerated secrets invalidate every active session on upgrade.
2. Full config as Secret
The chart renders the full AuthPlane config as a Kubernetes Secret (not ConfigMap) because it can contain sensitive values (DSN, Vault tokens). For GitOps:
existingConfigSecret: my-sealed-config
3. Database password
# Option A — Inline
externalDatabase:
password: secret
# Option B — Pre-existing Secret with the full DSN
externalDatabase:
existingSecret: my-db-secret
existingSecretKey: dsn
Observability
Prometheus (ServiceMonitor)
config:
observability:
metrics:
provider: prometheus
path: /metrics
serviceMonitor:
enabled: true
interval: 15s
OpenTelemetry (logs + traces + metrics)
config:
observability:
logging:
outputs:
otel: true
otel_endpoint: otel-collector.monitoring:4317
insecure: true
tracing:
enabled: true
endpoint: otel-collector.monitoring:4317
insecure: true
sample_rate: 1.0
metrics:
provider: both
otel_endpoint: otel-collector.monitoring:4317
insecure: true
Full metric catalog and Grafana dashboards in Guides: Monitoring.
Production checklist
Scaling
- PostgreSQL mode — AuthPlane is stateless. Scale horizontally with HPA.
- Signing keys — use Vault Transit (
vault.signing.enabled: true) for multi-replica. With keyfile, all replicas share the PVC (ReadWriteManyrequired, or co-locate pods on the same node). - Session affinity — not required. Sessions are stored in signed cookies, not server-side.
- Database connections — total =
config.storage.postgres.max_conns × replicas. Size your Postgres accordingly.
Local testing with Kind
For iterating on the chart itself, kind is the fast local loop. Key steps:
kind create cluster --name authplane-test
docker build -t authplane:local .
kind load docker-image authplane:local --name authplane-test
helm dependency update charts/authplane
helm install authplane charts/authplane -f values-sqlite.yaml \
--set image.repository=authplane --set image.tag=local
kubectl port-forward svc/authplane 9000:9000 9001:9001
Uninstallation
helm uninstall authplane
Note — PVCs are not deleted automatically. Remove them manually if no longer needed:
kubectl delete pvc -l app.kubernetes.io/instance=authplane
Chart reference
Every configurable parameter lives in the chart’s values.yaml — pull it from the OCI registry (helm show values oci://ghcr.io/authplane/charts/authplane) to see the shipped defaults. For raw manifests (no Helm), template the chart with helm template … and commit the output.
Related
- Operate overview — mode picker
- Docker Compose — same setup at single-host scale
- Standalone binary — same setup on a Linux host without containers
- Vault Transit — HSM-grade signing detail
- Guides: Federate to your IdP — OIDC provider setup end to end
- Guides: Monitoring — Prometheus + OTEL full setup
- Configuration reference — every env var