feat: add DSN/URL connection string support for PostgreSQL and Redis (#11729)

This commit is contained in:
Mridang Agarwalla
2026-03-20 15:21:15 +00:00
committed by GitHub
parent dad4565d5d
commit 471ed4a5d7
25 changed files with 810 additions and 302 deletions
@@ -15,94 +15,66 @@ Caching is an experimental beta feature. See the [Caches](/self-hosting/manage/c
Zitadel supports Redis or any Redis-compatible store (for example, Valkey) in standalone mode. Sentinel and Redis Cluster are not supported because the cache connector expects a single endpoint and does not implement cluster routing or sentinel failover logic.
<Callout type="note">
Zitadel supports server-auth TLS (`EnableTLS: true`) but does not support client certificates/mTLS for cache connections.
Zitadel supports server-auth TLS (`rediss://` scheme) but does not support client certificates/mTLS for cache connections.
</Callout>
Each cache uses its own Redis database index. Set `DBOffset` so Zitadel-owned indexes do not overlap with other applications. Zitadel issues `FLUSHDB` on its cache databases, so sharing an index with other apps is unsafe.
## Connect without authentication (development only)
Pass the Redis URL as an environment variable:
```yaml
zitadel:
configmapConfig:
Caches:
Connectors:
Redis:
Enabled: true
Addr: "redis-master.caching.svc.cluster.local:6379"
DBOffset: 10
Instance:
Connector: "redis"
MaxAge: 1h
LastUseAge: 10m
Organization:
Connector: "redis"
MaxAge: 1h
LastUseAge: 10m
env:
- name: ZITADEL_CACHES_CONNECTORS_REDIS_URL
value: "redis://redis-master.caching.svc.cluster.local:6379"
- name: ZITADEL_CACHES_CONNECTORS_REDIS_ENABLED
value: "true"
```
Use this only on isolated clusters where the Redis endpoint does not require authentication.
## Connect with password from a Kubernetes Secret
Keep credentials in a secret while the rest of the cache configuration stays in values:
```yaml
zitadel:
configSecretName: zitadel-cache-credentials
configmapConfig:
Caches:
Connectors:
Redis:
Enabled: true
Addr: "redis.database.svc.cluster.local:6379"
DBOffset: 10
Instance:
Connector: "redis"
MaxAge: 1h
LastUseAge: 10m
Organization:
Connector: "redis"
MaxAge: 1h
LastUseAge: 10m
```
Create the secret with your Redis ACL user and password:
Store the Redis URL (with credentials) in a secret and pass it as an environment variable:
```bash
kubectl create secret generic zitadel-cache-credentials \
--from-literal=config.yaml="
Caches:
Connectors:
Redis:
Username: cache-user # Omit for default user
Password: your-redis-password
"
--from-literal=url="redis://cache-user:[email protected]:6379"
```
```yaml
zitadel:
env:
- name: ZITADEL_CACHES_CONNECTORS_REDIS_URL
valueFrom:
secretKeyRef:
name: zitadel-cache-credentials
key: url
- name: ZITADEL_CACHES_CONNECTORS_REDIS_ENABLED
value: "true"
```
## Connect with TLS (Redis or Valkey)
Enable TLS when your endpoint requires encryption. This uses server-auth TLS with the container's trust store.
Use the `rediss://` scheme (note the double `s`) when your endpoint requires encryption. This uses server-auth TLS with the container's trust store.
```bash
kubectl create secret generic zitadel-cache-credentials \
--from-literal=url="rediss://cache-user:[email protected]:6380"
```
```yaml
zitadel:
configSecretName: zitadel-cache-credentials
configmapConfig:
Caches:
Connectors:
Redis:
Enabled: true
Addr: "redis.example.com:6380"
EnableTLS: true
DBOffset: 10
Instance:
Connector: "redis"
MaxAge: 1h
LastUseAge: 10m
Organization:
Connector: "redis"
MaxAge: 1h
LastUseAge: 10m
env:
- name: ZITADEL_CACHES_CONNECTORS_REDIS_URL
valueFrom:
secretKeyRef:
name: zitadel-cache-credentials
key: url
- name: ZITADEL_CACHES_CONNECTORS_REDIS_ENABLED
value: "true"
```
Reuse the secret from the previous section for credentials if needed. Custom certificate authorities must be added to the container's trust bundle (for example, by mounting a CA file into the pod); client certificate authentication is not supported.
Custom certificate authorities must be added to the container's trust bundle (for example, by mounting a CA file into the pod); client certificate authentication is not supported.
@@ -162,36 +162,23 @@ kubectl create secret generic zitadel-masterkey \
--from-literal=masterkey="$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)"
```
Create a config secret for sensitive values. Save this as `zitadel-config-secret.yaml`:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: zitadel-config-secret
type: Opaque
stringData:
config.yaml: |
Database:
Postgres:
User:
Password: "secure-app-password"
Admin:
Password: "secure-admin-password"
```
Apply the secret:
Create a secret containing the database DSN:
```bash
kubectl apply --filename zitadel-config-secret.yaml
kubectl create secret generic zitadel-config-secret \
--from-literal=dsn="postgresql://zitadel:[email protected]:5432/zitadel?sslmode=disable"
```
Reference it in your values:
Pass it to Zitadel as an environment variable:
```yaml
zitadel:
configSecretName: zitadel-config-secret
configSecretKey: config.yaml
env:
- name: ZITADEL_DATABASE_POSTGRES_DSN
valueFrom:
secretKeyRef:
name: zitadel-config-secret
key: dsn
```
## Scaling and Availability
@@ -14,66 +14,45 @@ Zitadel requires PostgreSQL 14 or later. The chart supports multiple ways to con
This method connects to PostgreSQL without encryption. Only use this for testing or when the database is on a private network with no risk of interception.
```yaml
zitadel:
configmapConfig:
Database:
Postgres:
Host: "postgres.database.svc.cluster.local"
Port: 5432
Database: "zitadel"
User:
Username: "zitadel"
Password: "your-password"
SSL:
Mode: "disable"
Admin:
Username: "postgres"
Password: "your-admin-password"
SSL:
Mode: "disable"
Store the DSN in a Kubernetes Secret and pass it as an environment variable:
```bash
kubectl create secret generic zitadel-db-credentials \
--from-literal=dsn="postgresql://zitadel:[email protected]:5432/zitadel?sslmode=disable"
```
The `SSL.Mode: "disable"` setting turns off TLS entirely. Traffic between Zitadel and PostgreSQL is unencrypted.
```yaml
zitadel:
env:
- name: ZITADEL_DATABASE_POSTGRES_DSN
valueFrom:
secretKeyRef:
name: zitadel-db-credentials
key: dsn
```
The `sslmode=disable` setting turns off TLS entirely. Traffic between Zitadel and PostgreSQL is unencrypted.
### Connecting with Credentials and TLS
This method connects to PostgreSQL with TLS encryption but without certificate verification. Use this when you trust the network path but want encryption in transit.
```yaml
zitadel:
configSecretName: zitadel-db-credentials
configmapConfig:
Database:
Postgres:
Host: "postgres.database.svc.cluster.local"
Port: 5432
Database: "zitadel"
User:
Username: "zitadel"
SSL:
Mode: "require"
Admin:
Username: "postgres"
SSL:
Mode: "require"
```
Create a secret for the database passwords:
```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
"
--from-literal=dsn="postgresql://zitadel:[email protected]:5432/zitadel?sslmode=require"
```
The `SSL.Mode: "require"` setting enforces TLS but does not verify the server certificate. This protects against passive eavesdropping but not against man-in-the-middle attacks.
```yaml
zitadel:
env:
- name: ZITADEL_DATABASE_POSTGRES_DSN
valueFrom:
secretKeyRef:
name: zitadel-db-credentials
key: dsn
```
The `sslmode=require` setting enforces TLS but does not verify the server certificate. This protects against passive eavesdropping but not against man-in-the-middle attacks.
### Connecting with Certificates
@@ -81,23 +60,14 @@ This method connects to PostgreSQL with full TLS verification using certificates
```yaml
zitadel:
configSecretName: zitadel-db-credentials
env:
- name: ZITADEL_DATABASE_POSTGRES_DSN
valueFrom:
secretKeyRef:
name: zitadel-db-credentials
key: dsn
dbSslCaCrt: "ca.crt"
dbSslCaCrtSecret: "postgres-ca-cert"
configmapConfig:
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"
```
Create a secret containing the CA certificate:
@@ -107,18 +77,11 @@ kubectl create secret generic postgres-ca-cert \
--from-file=ca.crt=/path/to/your/ca-certificate.crt
```
Create a secret for the database passwords:
Create a secret containing the DSN with full verification:
```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
"
--from-literal=dsn="postgresql://zitadel:[email protected]:5432/zitadel?sslmode=verify-full&sslrootcert=/db-ssl-ca-crt/ca.crt"
```
The `SSL.Mode: "verify-full"` setting enforces TLS and verifies that the server certificate is signed by the CA and that the server hostname matches the certificate. This provides full protection against eavesdropping and man-in-the-middle attacks. The `dbSslCaCrtSecret` references the Kubernetes Secret containing the CA certificate, and `dbSslCaCrt` specifies the key within that secret.
The `sslmode=verify-full` setting enforces TLS and verifies that the server certificate is signed by the CA and that the server hostname matches the certificate. This provides full protection against eavesdropping and man-in-the-middle attacks. The `dbSslCaCrtSecret` references the Kubernetes Secret containing the CA certificate, and `dbSslCaCrt` specifies the key within that secret.
@@ -76,7 +76,7 @@ The bundled PostgreSQL subchart is for quickstart use only. You can replace it i
| Component | How to replace |
|-----------|---------------|
| **Database** | Set `postgresql.enabled: false` and configure `zitadel.configmapConfig.Database` pointing to your own PostgreSQL |
| **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](/self-hosting/deploy/kubernetes/caching)) |
@@ -106,16 +106,9 @@ The ZITADEL management console [requires end-to-end HTTP/2 support](/self-hostin
kubectl create secret generic zitadel-masterkey \
--from-literal=masterkey="$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)"
# Database credentials
# Database credentials — store the full DSN
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
"
--from-literal=dsn="postgresql://zitadel:[email protected]:5432/postgres?sslmode=verify-full"
```
### Configure values
@@ -127,26 +120,18 @@ replicaCount: 2
zitadel:
masterkeySecretName: zitadel-masterkey
configSecretName: zitadel-db-credentials
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
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:
@@ -132,13 +132,22 @@ When scaling Zitadel horizontally, ensure your PostgreSQL database can handle th
Example connection pooling setup with PgBouncer:
```bash
kubectl create secret generic zitadel-db-credentials \
--from-literal=dsn="postgresql://zitadel:[email protected]:6432/zitadel?sslmode=disable"
```
```yaml
zitadel:
env:
- name: ZITADEL_DATABASE_POSTGRES_DSN
valueFrom:
secretKeyRef:
name: zitadel-db-credentials
key: dsn
configmapConfig:
Database:
Postgres:
Host: "pgbouncer.database.svc.cluster.local"
Port: 6432
MaxOpenConns: 20
MaxIdleConns: 10
```
@@ -40,7 +40,7 @@ LATEST=$(curl -i https://github.com/zitadel/zitadel/releases/latest | grep locat
## Run Zitadel
```bash
ZITADEL_DATABASE_POSTGRES_HOST=localhost ZITADEL_DATABASE_POSTGRES_PORT=5432 ZITADEL_DATABASE_POSTGRES_DATABASE=zitadel ZITADEL_DATABASE_POSTGRES_USER_USERNAME=zitadel ZITADEL_DATABASE_POSTGRES_USER_PASSWORD=zitadel ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE=disable ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME=root ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD=postgres ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE=disable ZITADEL_EXTERNALSECURE=false zitadel start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled
ZITADEL_DATABASE_POSTGRES_DSN=postgresql://root:postgres@localhost:5432/postgres?sslmode=disable ZITADEL_EXTERNALSECURE=false zitadel start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled
```
<DefaultUser components={props.components} />
@@ -62,7 +62,7 @@ ZITADEL_DATABASE_POSTGRES_HOST=localhost ZITADEL_DATABASE_POSTGRES_PORT=5432 ZIT
### Setup Zitadel with a service account
```bash
ZITADEL_DATABASE_POSTGRES_HOST=localhost ZITADEL_DATABASE_POSTGRES_PORT=5432 ZITADEL_DATABASE_POSTGRES_DATABASE=zitadel ZITADEL_DATABASE_POSTGRES_USER_USERNAME=zitadel ZITADEL_DATABASE_POSTGRES_USER_PASSWORD=zitadel ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE=disable ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME=root ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE=disable ZITADEL_EXTERNALSECURE=false ZITADEL_FIRSTINSTANCE_MACHINEKEYPATH=/tmp/zitadel-admin-sa.json ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME=zitadel-admin-sa ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME=Admin ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINEKEY_TYPE=1 zitadel start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled
ZITADEL_DATABASE_POSTGRES_DSN=postgresql://root:postgres@localhost:5432/postgres?sslmode=disable ZITADEL_EXTERNALSECURE=false ZITADEL_FIRSTINSTANCE_MACHINEKEYPATH=/tmp/zitadel-admin-sa.json ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME=zitadel-admin-sa ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME=Admin ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINEKEY_TYPE=1 zitadel start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled
# then you can move your machine key
mv /tmp/zitadel-admin-sa.json $HOME/zitadel-admin-sa.json
@@ -45,7 +45,7 @@ LATEST=$(curl -i https://github.com/zitadel/zitadel/releases/latest | grep locat
## Run ZITADEL
```bash
ZITADEL_DATABASE_POSTGRES_HOST=localhost ZITADEL_DATABASE_POSTGRES_PORT=5432 ZITADEL_DATABASE_POSTGRES_DATABASE=zitadel ZITADEL_DATABASE_POSTGRES_USER_USERNAME=zitadel ZITADEL_DATABASE_POSTGRES_USER_PASSWORD=zitadel ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE=disable ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME=$(whoami) ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD=postgres ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE=disable ZITADEL_EXTERNALSECURE=false zitadel start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled
ZITADEL_DATABASE_POSTGRES_DSN="postgresql://root:postgres@localhost:5432/postgres?sslmode=disable" ZITADEL_EXTERNALSECURE=false zitadel start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled
```
<DefaultUser components={props.components} />
@@ -57,7 +57,7 @@ ZITADEL_DATABASE_POSTGRES_HOST=localhost ZITADEL_DATABASE_POSTGRES_PORT=5432 ZIT
### Setup ZITADEL with a service account
```bash
ZITADEL_DATABASE_POSTGRES_HOST=localhost ZITADEL_DATABASE_POSTGRES_PORT=5432 ZITADEL_DATABASE_POSTGRES_DATABASE=zitadel ZITADEL_DATABASE_POSTGRES_USER_USERNAME=zitadel ZITADEL_DATABASE_POSTGRES_USER_PASSWORD=zitadel ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE=disable ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME=$(whoami) ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE=disable ZITADEL_EXTERNALSECURE=false ZITADEL_FIRSTINSTANCE_MACHINEKEYPATH=/tmp/zitadel-admin-sa.json ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME=zitadel-admin-sa ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME=Admin ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINEKEY_TYPE=1 zitadel start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled
ZITADEL_DATABASE_POSTGRES_DSN="postgresql://root:postgres@localhost:5432/postgres?sslmode=disable" ZITADEL_EXTERNALSECURE=false ZITADEL_FIRSTINSTANCE_MACHINEKEYPATH=/tmp/zitadel-admin-sa.json ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME=zitadel-admin-sa ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME=Admin ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINEKEY_TYPE=1 zitadel start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled
# then you can move your machine key
mv /tmp/zitadel-admin-sa.json $HOME/zitadel-admin-sa.json