docs: simplify helm chart guide (#11781)

## Summary

Rewrites the Kubernetes quickstart (Stage 1) to match the simplicity of
the Docker Compose quickstart. With the new bundled chart, users deploy
the full stack — PostgreSQL, Traefik, ZITADEL API, ZITADEL Login — with
two copy-paste blocks and no prerequisites beyond a Kubernetes cluster.

## Before / After

**Before:**
- Requires a pre-installed ingress controller
- 4 manual steps: install PostgreSQL separately, create secrets, wire DB
config, install ZITADEL
- Users need to understand Helm dependency management

**After:**
```bash
mkdir zitadel-helm && cd zitadel-helm &&
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel-charts/main/examples/0-quickstart/quickstart-values.yaml
```
```bash
helm repo add zitadel https://charts.zitadel.com &&
helm repo add bitnami https://charts.bitnami.com/bitnami &&
helm repo add traefik https://traefik.github.io/charts &&
helm repo update &&
helm upgrade --install zitadel zitadel/zitadel --values quickstart-values.yaml --wait
```

Same UX as the Docker Compose quickstart. Mirrors the GitLab chart
pattern.

## Changes to `kubernetes/index.mdx`

### Stage 1 — Quickstart (full rewrite)
- Prerequisites reduced to: a Kubernetes cluster (1.30+), kubectl, Helm
- k3d moved to an optional callout tip (no longer required)
- Install section: `mkdir` → `curl` → `helm upgrade --install --wait`
(two copy-paste blocks, same pattern as compose)
- `helm upgrade --install` instead of `helm install` — idempotent, safe
to re-run
- "Swap out components" table: shows how to replace PostgreSQL, Traefik,
or add Redis
- Callouts: masterkey warning, stack architecture diagram

### Stage 2+ (unchanged)
Production cluster setup, TLS, cert-manager, etc. are untouched.

## Related
- Chart changes PR:
[zitadel/zitadel-charts#560](https://github.com/zitadel/zitadel-charts/pull/560)

---------

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Florian Forster
2026-03-17 10:36:23 +00:00
committed by GitHub
co-authored by Copilot
parent 921414fa16
commit 3fdac54878
3 changed files with 207 additions and 265 deletions
@@ -1,63 +1,232 @@
---
title: Kubernetes
title: Deploy ZITADEL on Kubernetes
sidebar_label: Overview
description: Deploy ZITADEL on Kubernetes using Helm charts for scalable identity management
description: Deploy ZITADEL on Kubernetes using Helm — from a local 5-minute quickstart to a hardened production cluster.
---
This guide takes you from zero to a running ZITADEL instance on Kubernetes and then shows you how to harden it for production.
## Stage 1 — Quickstart
Deploy Zitadel, the open-source identity and access management platform, on Kubernetes using Helm.
The ZITADEL chart ships with an optional PostgreSQL subchart so a single `helm install` deploys the database alongside ZITADEL. You still need an ingress controller running in your cluster.
## What's in the Chart
### Prerequisites
By default, this chart installs a highly available Zitadel deployment. The chart deploys:
- A Kubernetes cluster (1.30+)
- An ingress controller (e.g. Traefik, NGINX)
- [kubectl](https://kubernetes.io/docs/tasks/tools/)
- [Helm](https://helm.sh/docs/intro/install/) 3.x or 4.x
- A Zitadel **init job** that prepares the database schema
- A Zitadel **setup job** that bootstraps the first instance and admin user
- A Zitadel **deployment** with configurable replicas for high availability
<Callout type="info">
No cluster yet? [k3d](https://k3d.io/) is a quick way to spin one up locally — it runs k3s in Docker, with Traefik as the built-in ingress controller:
```bash
k3d cluster create zitadel --port "80:80@loadbalancer"
```
</Callout>
The execution order is orchestrated using Helm hooks on installations and upgrades.
### Install the full stack
## Architecture Overview
```bash
mkdir zitadel-helm && cd zitadel-helm &&
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel-charts/main/examples/0-quickstart/quickstart-values.yaml
```
Zitadel is deployed as two separate containers within your Kubernetes cluster. The main Zitadel container handles all API traffic, the console UI, and OIDC endpoints, while the Login container serves the login UI at the `/ui/v2/login` path. Both containers are stateless and can be scaled horizontally to handle increased load.
Edit `quickstart-values.yaml` before installing. For a local k3d or k3s cluster, these values usually work as-is:
Traffic enters the cluster through an ingress controller such as Traefik or NGINX. The chart creates two separate Ingress resources, one for each container, both typically sharing the same domain and TLS certificate.
```yaml
zitadel:
configmapConfig:
ExternalDomain: localhost
ExternalPort: 80
TLS termination happens at the ingress controller, which forwards unencrypted HTTP/2 traffic to the pods. cert-manager or the ingress controller's built-in ACME support can be used to automatically obtain and renew TLS certificates from Let's Encrypt.
ingress:
className: traefik
Zitadel stores all persistent data in PostgreSQL. The database holds user accounts, organizations, projects, applications, sessions, and all other identity data. Zitadel uses two database connections: an admin connection for schema migrations during startup and an application connection for normal operations. Both connections should use TLS in production environments.
login:
ingress:
className: traefik
```
For observability, Zitadel pushes traces to an OpenTelemetry Collector using the OTLP protocol over gRPC. Prometheus scrapes metrics from the `/debug/metrics` endpoint on the Zitadel pods. Both containers write structured logs to stdout, which must be collected by a log collector such as Fluent Bit or Vector. The Login container does not support tracing or metrics, only log collection. All observability data can be forwarded to a unified backend such as Grafana, OpenObserve, or your preferred monitoring stack.
If you are using a different ingress controller, replace both `className` values with that controller's IngressClass name.
## Prerequisites at a Glance
```bash
helm repo add zitadel https://charts.zitadel.com &&
helm repo update &&
helm upgrade --install zitadel zitadel/zitadel --values quickstart-values.yaml --wait
```
| Requirement | Minimum Version |
|-------------|-----------------|
| Kubernetes | 1.30+ |
| Helm | 3.x or 4.x |
| kubectl | 1.30+ |
| PostgreSQL | 14+ |
That's it. Visit [http://localhost/ui/[email protected]](http://localhost/ui/[email protected]) and log in with password `Password1!`.
You also need:
<Callout type="warn">
The masterkey [encrypts sensitive data at rest](/concepts/architecture/secrets). The `quickstart-values.yaml` file contains a **dev placeholder** masterkey — generate a real one before any non-local deployment:
```bash
tr -dc A-Za-z0-9 </dev/urandom | head -c 32
```
Once ZITADEL has been initialized with a masterkey, it **cannot be changed** without losing access to encrypted data.
</Callout>
<Callout type="info">
The stack runs: your ingress controller → **ZITADEL API** (Go) + **ZITADEL Login** (Next.js) → **PostgreSQL** (bundled).
See [HTTP/2](/self-hosting/manage/http2) for requirements on how the ingress controller must forward traffic to ZITADEL pods.
</Callout>
### Swap out components
The bundled PostgreSQL subchart is for quickstart use only. You can replace it independently:
| Component | How to replace |
|-----------|---------------|
| **Database** | Set `postgresql.enabled: false` and configure `zitadel.configmapConfig.Database` pointing to your own PostgreSQL |
| **Ingress controller** | Update `ingress.className` (and `login.ingress.className`) to match your controller's IngressClass |
| **Caching** | Add Redis by configuring `zitadel.configmapConfig.Caches` (see [Caching](/self-hosting/deploy/kubernetes/caching)) |
## Stage 2 — Production Cluster
For production you need:
- A Kubernetes cluster (1.30+)
- An ingress controller (Traefik, NGINX, or cloud-native)
- A domain with DNS configured
- TLS certificates (via cert-manager, ACME, or manual)
- TLS certificates (via cert-manager, ACME, or manually)
- PostgreSQL 14+ (managed service or in-cluster)
- Helm 3.x+
## Quick Links
<Callout type="warn">
The ZITADEL management console [requires end-to-end HTTP/2 support](/self-hosting/manage/http2). Ensure your ingress controller is configured to forward HTTP/2 (h2c) traffic to ZITADEL pods.
</Callout>
| Resource | Link |
|----------|------|
| Helm Chart Repository | [charts.zitadel.com](https://charts.zitadel.com) |
| Source Code | [github.com/zitadel/zitadel-charts](https://github.com/zitadel/zitadel-charts) |
| Zitadel Documentation | [zitadel.com/docs](https://zitadel.com/docs) |
| Artifact Hub | [artifacthub.io/packages/helm/zitadel/zitadel](https://artifacthub.io/packages/helm/zitadel/zitadel) |
### Create secrets
## Next Steps
```bash
# Masterkey — generate once, store safely, cannot be changed after first run
kubectl create secret generic zitadel-masterkey \
--from-literal=masterkey="$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)"
- [Installation](/self-hosting/deploy/kubernetes/installation) — Get Zitadel running
- [Configuration](/self-hosting/deploy/kubernetes/configuration) — Customize your deployment
- [Configuring the Ingress](/self-hosting/deploy/kubernetes/ingress) — Set up ingress for Zitadel and Login containers
- [Configuring the Database](/self-hosting/deploy/kubernetes/database) — Connect to PostgreSQL
- [Operations](/self-hosting/deploy/kubernetes/operations) — Upgrades and scaling
- [Uninstalling](/self-hosting/deploy/kubernetes/uninstalling) — Remove Zitadel from your cluster
# Database credentials
kubectl create secret generic zitadel-db-credentials \
--from-literal=config.yaml="
Database:
Postgres:
User:
Password: your-secure-app-password
Admin:
Password: your-secure-admin-password
"
```
### Configure values
Save the following as `values.yaml`. Replace `zitadel.example.com` with your domain:
```yaml
replicaCount: 2
zitadel:
masterkeySecretName: zitadel-masterkey
configSecretName: zitadel-db-credentials
configmapConfig:
ExternalDomain: "zitadel.example.com"
ExternalSecure: true
ExternalPort: 443
TLS:
Enabled: false
Database:
Postgres:
Host: "postgres.database.svc.cluster.local"
Port: 5432
Database: "zitadel"
User:
Username: "zitadel"
SSL:
Mode: "verify-full"
Admin:
Username: "postgres"
SSL:
Mode: "verify-full"
FirstInstance:
Org:
Human:
UserName: "admin"
Email: "[email protected]"
Password: "YourSecurePassword123!"
PasswordChangeRequired: true
ingress:
enabled: true
className: traefik
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
hosts:
- host: zitadel.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: zitadel-tls
hosts:
- zitadel.example.com
login:
ingress:
enabled: true
className: traefik
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
hosts:
- host: zitadel.example.com
paths:
- path: /ui/v2/login
pathType: Prefix
tls:
- secretName: zitadel-tls
hosts:
- zitadel.example.com
podDisruptionBudget:
enabled: true
minAvailable: 1
```
### Install
```bash
helm repo add zitadel https://charts.zitadel.com && helm repo update
helm install zitadel zitadel/zitadel --values values.yaml --wait
```
### Verify
Watch the pods come up:
```bash
kubectl get pods --watch
```
You should see the `zitadel-init` and `zitadel-setup` jobs complete, followed by the `zitadel` deployment pods becoming Ready.
Check the Helm release status:
```bash
helm status zitadel
```
Access the console at `https://zitadel.example.com/ui/console`.
## What's next
Review the [Production Checklist](/self-hosting/manage/productionchecklist) before going live, then use the detailed guides for each concern:
| Guide | Description |
|-------|-------------|
| [Configuration](/self-hosting/deploy/kubernetes/configuration) | All configmap and secret options, autoscaling, security contexts |
| [Ingress](/self-hosting/deploy/kubernetes/ingress) | Traefik, NGINX, and cloud-native ingress setup |
| [Database](/self-hosting/deploy/kubernetes/database) | PostgreSQL TLS modes, credentials, and connection pooling |
| [Operations](/self-hosting/deploy/kubernetes/operations) | Upgrades, manual and automatic scaling, resource limits |
| [Caching](/self-hosting/deploy/kubernetes/caching) | Redis/Valkey caching configuration |
| [Observability](/self-hosting/deploy/kubernetes/observability) | Traces (OTLP), Prometheus metrics, and log collection |
| [Uninstalling](/self-hosting/deploy/kubernetes/uninstalling) | Remove ZITADEL from your cluster |
@@ -1,226 +0,0 @@
---
title: Installation
description: "Deploy ZITADEL on Kubernetes using Helm with a PostgreSQL database and an Ingress controller for external access"
sidebar_label: Installation
---
This guide walks you through deploying Zitadel on Kubernetes using Helm.
## Prerequisites
You need a running Kubernetes cluster. Supported versions:
- Kubernetes 1.30.13+
- Kubernetes 1.31.9+
- Kubernetes 1.32.5+
- Kubernetes 1.33.1+
### An Ingress Controller
Zitadel requires an ingress controller to route traffic to the Zitadel and Login pods. The chart creates standard Kubernetes Ingress resources that work with any ingress controller, including Traefik, NGINX, and cloud-native options like AWS ALB or GCP Cloud Load Balancer.
TLS must be terminated at the ingress controller. The Zitadel and Login containers do not handle TLS termination. Your ingress controller is responsible for obtaining and renewing TLS certificates (via ACME, cert-manager, or manually), terminating TLS connections, and forwarding traffic to the Zitadel pods.
Without an ingress controller and valid TLS certificates, Zitadel will not work. The chart configures the ingress to use HTTP/2 for communication between the ingress controller and Zitadel pods.
### A PostgreSQL Database
Zitadel requires PostgreSQL 14 or later. You can use a managed PostgreSQL service such as Amazon RDS, Google Cloud SQL, or Azure Database for PostgreSQL, or run PostgreSQL in your cluster using a Helm chart or operator. The database must be accessible from the Zitadel pods.
Zitadel uses two database users: an admin user for schema migrations and an application user for normal operations. See the [Database](./database) page for connection options.
### Helm
Install Helm 3.x or 4.x following the [official instructions](https://helm.sh/docs/intro/install/). Verify your installation:
```bash
helm version
```
### kubectl
Install kubectl following the [Kubernetes documentation](https://kubernetes.io/docs/tasks/tools/). Verify connectivity to your cluster:
```bash
kubectl cluster-info
```
## Add the Helm Repository
Add the Zitadel Helm repository:
```bash
helm repo add zitadel https://charts.zitadel.com
```
Update your local repository cache:
```bash
helm repo update
```
Verify the repository was added:
```bash
helm search repo zitadel
```
You should see output similar to:
```
NAME CHART VERSION APP VERSION DESCRIPTION
zitadel/zitadel 9.x.x v4.x.x A Helm chart for Zitadel
```
## Install
### Step 1: Create Secrets
Create the masterkey secret:
```bash
kubectl create secret generic zitadel-masterkey \
--from-literal=masterkey="$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)"
```
Create the database credentials secret:
```bash
kubectl create secret generic zitadel-db-credentials \
--from-literal=config.yaml="
Database:
Postgres:
User:
Password: your-secure-app-password
Admin:
Password: your-secure-admin-password
"
```
### Step 2: Configure Values
Create a `values.yaml` file. Replace `zitadel.example.com` with your actual domain:
```yaml
replicaCount: 2
zitadel:
masterkeySecretName: zitadel-masterkey
configSecretName: zitadel-db-credentials
configmapConfig:
ExternalDomain: "zitadel.example.com"
ExternalSecure: true
ExternalPort: 443
TLS:
Enabled: false
Database:
Postgres:
Host: "postgres.database.svc.cluster.local"
Port: 5432
Database: "zitadel"
User:
Username: "zitadel"
SSL:
Mode: "verify-full"
Admin:
Username: "postgres"
SSL:
Mode: "verify-full"
FirstInstance:
Org:
Human:
UserName: "admin"
FirstName: "Zitadel"
LastName: "Admin"
Email: "[email protected]"
Password: "YourSecurePassword123!"
PasswordChangeRequired: true
ingress:
enabled: true
className: traefik
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
hosts:
- host: zitadel.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: zitadel-tls
hosts:
- zitadel.example.com
podDisruptionBudget:
enabled: true
minAvailable: 1
```
### Step 3: Install the Chart
```bash
helm install zitadel zitadel/zitadel --values values.yaml
```
Install Zitadel:
```bash
helm install my-zitadel zitadel/zitadel --values values.yaml
```
## Verify Deployment
Watch the pods come up:
```bash
kubectl get pods --watch
```
You should see:
1. `my-zitadel-init-*` job completes
2. `my-zitadel-setup-*` job completes
3. `my-zitadel-*` deployment pods become Ready
Check the deployment status:
```bash
helm status my-zitadel
```
Verify all pods are running:
```bash
kubectl get pods --selector app.kubernetes.io/name=zitadel
```
You should see output similar to:
```
NAME READY STATUS RESTARTS AGE
my-zitadel-xxxxxxxxx-xxxxx 1/1 Running 0 5m
my-zitadel-xxxxxxxxx-xxxxx 1/1 Running 0 5m
```
## Access the UI
Access Zitadel at:
```
https://zitadel.example.com/ui/console
```
## First Login
Access the console at `https://zitadel.example.com/ui/console` and log in with the admin credentials configured in your `values.yaml`.
## Next Steps
- [Configuration](./configuration) — Customize Zitadel settings
- [Configuring the Ingress](./ingress) — Set up ingress for Zitadel and Login containers
- [Configuring the Database](./database) — Connect to PostgreSQL
- [Operations](./operations) — Learn about upgrades and scaling
- [Uninstalling](./uninstalling) — Remove Zitadel from your cluster
-1
View File
@@ -727,7 +727,6 @@ export const guidesSidebar: readonly SidebarItem[] = [
id: "self-hosting/deploy/kubernetes/index",
},
items: [
"self-hosting/deploy/kubernetes/installation",
"self-hosting/deploy/kubernetes/configuration",
"self-hosting/deploy/kubernetes/ingress",
"self-hosting/deploy/kubernetes/database",