mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 21:52:54 +00:00
## Description This PR updates the Kubernetes deployment documentation to address user feedback regarding our routing instructions. A user noted that standard `Ingress` is becoming outdated and requested configuration examples for the modern Gateway API. To support both existing and modern clusters, I have updated the guide to provide two clear pathways for exposing ZITADEL. ## Changes Included * **Prerequisites updated:** Mentioned Gateway API controllers alongside standard Ingress controllers. * **Refactored Stage 2 (Production):** Split the routing configuration into "Option A: Standard Ingress" and "Option B: Gateway API". * **Added YAML example:** Provided a sample `HTTPRoute` resource to route traffic to the `zitadel` and `zitadel-login` backend services. * **Terminology updates:** Broadened terms like "Ingress" to "Routing" or "Routing controller" where applicable.
267 lines
9.2 KiB
Plaintext
267 lines
9.2 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 a routing controller running in your cluster to expose the service.
|
||
|
||
### Prerequisites
|
||
|
||
- A Kubernetes cluster (1.30+)
|
||
- An Ingress controller (e.g., Traefik, NGINX) **OR** a Gateway API controller
|
||
- [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 standard Ingress 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/gateway controller → **ZITADEL API** (Go) + **ZITADEL Login** (Next.js) → **PostgreSQL** (bundled).
|
||
See [HTTP/2](/self-hosting/manage/http2) for requirements on how traffic must be forwarded to ZITADEL pods via h2c.
|
||
</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_DATABASE_POSTGRES_DSN` pointing to your own PostgreSQL. Use the `postgres` maintenance database so ZITADEL can create its own database during initialization. |
|
||
| **Routing / Ingress** | Update `ingress.className` to match your controller, or disable the Helm ingress (`ingress.enabled: false`) to manage your own Gateway API `HTTPRoute` resources. |
|
||
| **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+)
|
||
- A routing solution: Ingress controller (Traefik, NGINX) or a Gateway API implementation
|
||
- 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">
|
||
**PostgreSQL compatibility:** ZITADEL supports PostgreSQL **14–18**. When using **PostgreSQL 18**, ensure you run **ZITADEL v4.11.0 or newer**. For the most up-to-date compatibility matrix and configuration details, see [Database requirements](/self-hosting/manage/database).
|
||
</Callout>
|
||
|
||
<Callout type="warn">
|
||
The ZITADEL management console [requires end-to-end HTTP/2 support](/self-hosting/manage/http2). Ensure your routing 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 — store the full DSN
|
||
kubectl create secret generic zitadel-db-credentials \
|
||
--from-literal=dsn="postgresql://zitadel:[email protected]:5432/postgres?sslmode=verify-full"
|
||
```
|
||
|
||
### Configure values
|
||
|
||
Save the base configuration below as `values.yaml`. Replace `zitadel.example.com` with your domain:
|
||
|
||
```yaml
|
||
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
|
||
|
||
podDisruptionBudget:
|
||
enabled: true
|
||
minAvailable: 1
|
||
```
|
||
|
||
### Exposing ZITADEL (Choose your routing method)
|
||
|
||
ZITADEL can be exposed using standard Kubernetes **Ingress** or the newer **Gateway API**. Choose the method that matches your cluster's architecture.
|
||
|
||
#### Option A: Using Standard Ingress (e.g., Traefik)
|
||
|
||
Append the following routing block to your `values.yaml` to have Helm automatically generate standard `Ingress` objects:
|
||
|
||
```yaml
|
||
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
|
||
```
|
||
|
||
#### Option B: Using Gateway API
|
||
|
||
If your cluster utilizes the Kubernetes Gateway API, disable the Helm chart's built-in ingress generation by ensuring both `ingress.enabled: false` and `login.ingress.enabled: false` are set (which is the default).
|
||
|
||
After deploying the Helm chart, apply an `HTTPRoute` resource to route traffic to the ZITADEL services via your `Gateway`:
|
||
|
||
```yaml
|
||
apiVersion: gateway.networking.k8s.io/v1
|
||
kind: HTTPRoute
|
||
metadata:
|
||
name: zitadel-route
|
||
namespace: zitadel
|
||
spec:
|
||
parentRefs:
|
||
- name: your-cluster-gateway
|
||
namespace: gateway-system
|
||
hostnames:
|
||
- "zitadel.example.com"
|
||
rules:
|
||
- matches:
|
||
- path:
|
||
type: PathPrefix
|
||
value: /ui/v2/login
|
||
backendRefs:
|
||
- name: zitadel-login
|
||
port: 8080
|
||
- matches:
|
||
- path:
|
||
type: PathPrefix
|
||
value: /
|
||
backendRefs:
|
||
- name: zitadel
|
||
port: 8080
|
||
```
|
||
*(Note: Ensure your Gateway controller supports h2c/HTTP2 backend connections).*
|
||
|
||
### 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 |
|
||
| [Routing](/self-hosting/deploy/kubernetes/ingress) | Ingress, Gateway API, and cloud-native 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 | |