Files
zitadel/apps/docs/content/self-hosting/deploy/kubernetes/index.mdx
T
3fdac54878 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]>
2026-03-17 10:36:23 +00:00

233 lines
7.4 KiB
Plaintext

---
title: Deploy ZITADEL on Kubernetes
sidebar_label: Overview
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
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.
### Prerequisites
- 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
<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>
### Install the full stack
```bash
mkdir zitadel-helm && cd zitadel-helm &&
curl -fsSLO https://raw.githubusercontent.com/zitadel/zitadel-charts/main/examples/0-quickstart/quickstart-values.yaml
```
Edit `quickstart-values.yaml` before installing. For a local k3d or k3s cluster, these values usually work as-is:
```yaml
zitadel:
configmapConfig:
ExternalDomain: localhost
ExternalPort: 80
ingress:
className: traefik
login:
ingress:
className: traefik
```
If you are using a different ingress controller, replace both `className` values with that controller's IngressClass name.
```bash
helm repo add zitadel https://charts.zitadel.com &&
helm repo update &&
helm upgrade --install zitadel zitadel/zitadel --values quickstart-values.yaml --wait
```
That's it. Visit [http://localhost/ui/[email protected]](http://localhost/ui/[email protected]) and log in with password `Password1!`.
<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 manually)
- PostgreSQL 14+ (managed service or in-cluster)
- Helm 3.x+
<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>
### Create secrets
```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)"
# 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 |