Documentation Index
Fetch the complete documentation index at: https://mintlify.com/zitadel/zitadel/llms.txt
Use this file to discover all available pages before exploring further.
Deploy ZITADEL on Kubernetes for production-grade, multi-node installations.
Prerequisites
- Kubernetes cluster (1.23+)
- Helm 3.8+
kubectl configured to access your cluster
- PostgreSQL database (managed or self-hosted)
- Ingress controller (nginx, Traefik, or similar)
- TLS certificate (cert-manager or external)
Helm Chart Repository
ZITADEL’s official Helm chart is maintained in a separate repository:
helm repo add zitadel https://charts.zitadel.com
helm repo update
Chart source: github.com/zitadel/zitadel-charts
Quick Start
Create namespace
kubectl create namespace zitadel
Configure values
Create values.yaml:zitadel:
# Masterkey for encryption (exactly 32 characters)
masterkey: "MasterkeyNeedsToHave32Characters"
configmapConfig:
ExternalDomain: auth.example.com
ExternalPort: 443
ExternalSecure: true
TLS:
Enabled: false # TLS terminated at ingress
Database:
postgres:
Host: postgres.database.svc.cluster.local
Port: 5432
Database: zitadel
User:
Username: zitadel
Password: ""
SSL:
Mode: require
Admin:
Username: postgres
Password: ""
SSL:
Mode: require
ingress:
enabled: true
className: nginx
hosts:
- host: auth.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: zitadel-tls
hosts:
- auth.example.com
replicaCount: 2
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
Never commit the masterkey to version control. Use Kubernetes secrets or a secrets management solution.
Install ZITADEL
helm install zitadel zitadel/zitadel \
--namespace zitadel \
--values values.yaml \
--wait
Verify deployment
kubectl get pods -n zitadel
kubectl get ingress -n zitadel
Access ZITADEL at https://auth.example.com
Database Configuration
External PostgreSQL
For production, use a managed PostgreSQL service:
zitadel:
configmapConfig:
Database:
postgres:
Host: postgres.rds.amazonaws.com
Port: 5432
Database: zitadel
MaxOpenConns: 25
MaxIdleConns: 10
MaxConnLifetime: 30m
MaxConnIdleTime: 5m
User:
Username: zitadel
SSL:
Mode: require
# Optional: provide CA cert for SSL
RootCert: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
Admin:
Username: postgres
SSL:
Mode: require
# Provide credentials via secret
zitadelSecrets:
Database:
postgres:
User:
Password: "<zitadel-user-password>"
Admin:
Password: "<postgres-admin-password>"
In-Cluster PostgreSQL (Not Recommended for Production)
For development/testing only:
postgresql:
enabled: true
auth:
username: zitadel
password: zitadel
database: zitadel
postgresPassword: postgres
primary:
persistence:
size: 20Gi
zitadel:
configmapConfig:
Database:
postgres:
Host: zitadel-postgresql
Port: 5432
Database: zitadel
User:
Username: zitadel
SSL:
Mode: disable
Admin:
Username: postgres
SSL:
Mode: disable
Ingress Configuration
NGINX Ingress Controller
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
nginx.ingress.kubernetes.io/proxy-buffer-size: "128k"
hosts:
- host: auth.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: zitadel-tls
hosts:
- auth.example.com
Traefik Ingress
ingress:
enabled: true
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.entrypoints: websecure
hosts:
- host: auth.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: zitadel-tls
hosts:
- auth.example.com
gRPC Support
ZITADEL’s HTTP/2 backend supports gRPC, gRPC-Web, and REST simultaneously. No special ingress configuration is needed for gRPC.
For NGINX ingress with gRPC:
ingress:
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
nginx.ingress.kubernetes.io/grpc-backend: "true"
High Availability Configuration
Multiple Replicas
replicaCount: 3
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- zitadel
topologyKey: kubernetes.io/hostname
Pod Disruption Budget
podDisruptionBudget:
enabled: true
minAvailable: 1
Readiness and Liveness Probes
readinessProbe:
enabled: true
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
livenessProbe:
enabled: true
initialDelaySeconds: 60
periodSeconds: 30
failureThreshold: 3
Secrets Management
Using Kubernetes Secrets
Create a secret for the masterkey:
kubectl create secret generic zitadel-masterkey \
--from-literal=masterkey="$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)" \
-n zitadel
Reference in values:
zitadel:
masterkeySecretName: zitadel-masterkey
Using External Secrets Operator
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: zitadel-secrets
namespace: zitadel
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secretsmanager
kind: SecretStore
target:
name: zitadel-secrets
data:
- secretKey: masterkey
remoteRef:
key: zitadel/masterkey
- secretKey: database-password
remoteRef:
key: zitadel/database-password
Resource Requirements
Small Deployment (< 1000 users)
replicaCount: 2
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
zitadel:
configmapConfig:
Database:
postgres:
MaxOpenConns: 10
MaxIdleConns: 5
Medium Deployment (1000-10000 users)
replicaCount: 3
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
zitadel:
configmapConfig:
Database:
postgres:
MaxOpenConns: 25
MaxIdleConns: 10
Large Deployment (> 10000 users)
replicaCount: 5
resources:
requests:
cpu: 1000m
memory: 1Gi
limits:
cpu: 2000m
memory: 2Gi
zitadel:
configmapConfig:
Database:
postgres:
MaxOpenConns: 50
MaxIdleConns: 20
Monitoring and Observability
Prometheus Metrics
Enable Prometheus ServiceMonitor:
metrics:
enabled: true
serviceMonitor:
enabled: true
interval: 30s
zitadel:
configmapConfig:
Instrumentation:
Metric:
Exporter:
Type: prometheus
OpenTelemetry Tracing
zitadel:
configmapConfig:
Instrumentation:
ServiceName: zitadel
Trace:
Fraction: 1.0
Exporter:
Type: grpc
Endpoint: otel-collector.monitoring.svc.cluster.local:4317
Insecure: true
Structured Logging
zitadel:
configmapConfig:
Instrumentation:
Log:
Level: INFO
Format: json
Streams:
- runtime
- request
- event_handler
- queue
Multi-Tenancy
ZITADEL supports multiple instances (tenants) within a single deployment:
zitadel:
configmapConfig:
InstanceHostHeaders:
- x-zitadel-instance-host
PublicHostHeaders:
- x-zitadel-public-host
Configure your ingress to forward the appropriate headers:
ingress:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_set_header X-Zitadel-Instance-Host $host;
Upgrading
Update Helm chart
helm repo update
helm search repo zitadel/zitadel --versions
Upgrade installation
helm upgrade zitadel zitadel/zitadel \
--namespace zitadel \
--values values.yaml \
--wait
Verify upgrade
kubectl get pods -n zitadel
kubectl logs -n zitadel -l app.kubernetes.io/name=zitadel
Troubleshooting
Pod Crash Loop
# Check pod logs
kubectl logs -n zitadel <pod-name>
# Check events
kubectl describe pod -n zitadel <pod-name>
# Check configuration
kubectl get configmap -n zitadel zitadel-config -o yaml
Database Connection Issues
# Test database connectivity from pod
kubectl exec -n zitadel <pod-name> -- \
sh -c 'apk add postgresql-client && \
psql -h $ZITADEL_DATABASE_POSTGRES_HOST \
-U $ZITADEL_DATABASE_POSTGRES_USER_USERNAME \
-d $ZITADEL_DATABASE_POSTGRES_DATABASE \
-c "SELECT version();"'
Instance Not Found
Verify external domain configuration:
kubectl get configmap -n zitadel zitadel-config -o yaml | grep -A3 External
Ensure ExternalDomain, ExternalPort, and ExternalSecure match your ingress:
ExternalDomain: auth.example.com
ExternalPort: 443
ExternalSecure: true
Next Steps