apps/docs/content/self-hosting/deploy/kubernetes/index.mdx
This guide takes you from zero to a running ZITADEL instance on Kubernetes and then shows you how to harden it for production.
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.
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:
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.
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] and log in with password Password1!.
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_DATABASE_POSTGRES_DSN pointing to your own PostgreSQL. Use the postgres maintenance database so ZITADEL can create its own database during initialization |
| 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) |
For production you need:
# 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 — store the full DSN
kubectl create secret generic zitadel-db-credentials \
--from-literal=dsn="postgresql://zitadel:[email protected]:5432/postgres?sslmode=verify-full"
Save the following as values.yaml. Replace zitadel.example.com with your domain:
replicaCount: 2
zitadel:
masterkeySecretName: zitadel-masterkey
env:
- name: ZITADEL_DATABASE_POSTGRES_DSN
valueFrom:
secretKeyRef:
name: zitadel-db-credentials
key: dsn
configmapConfig:
ExternalDomain: "zitadel.example.com"
ExternalSecure: true
ExternalPort: 443
TLS:
Enabled: false
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
helm repo add zitadel https://charts.zitadel.com && helm repo update
helm install zitadel zitadel/zitadel --values values.yaml --wait
Watch the pods come up:
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:
helm status zitadel
Access the console at https://zitadel.example.com/ui/console.
Review the Production Checklist before going live, then use the detailed guides for each concern:
| Guide | Description |
|---|---|
| Configuration | All configmap and secret options, autoscaling, security contexts |
| Ingress | Traefik, NGINX, and cloud-native ingress setup |
| Database | PostgreSQL TLS modes, credentials, and connection pooling |
| Operations | Upgrades, manual and automatic scaling, resource limits |
| Caching | Redis/Valkey caching configuration |
| Observability | Traces (OTLP), Prometheus metrics, and log collection |
| Uninstalling | Remove ZITADEL from your cluster |