Keycloak
Deploy Keycloak on Kubernetes for identity and access management, SSO, OAuth 2.0,
OpenID Connect, and SAML. The chart supports explicit dev and production modes, Keycloak 26.6.1, bundled or
external PostgreSQL/MySQL-compatible databases, optional Gateway API routes, External Secrets Operator integration,
Prometheus metrics, S3-compatible database backup, and dual-stack Service fields.
Key Features
- Explicit runtime mode —
mode: devfor local testing andmode: productionfor hostname/database-backed deployments. - Keycloak 26.6.x alignment — image tag and app version use
quay.io/keycloak/keycloak:26.6.1. - Database choices — embedded H2 for dev only, bundled PostgreSQL/MySQL subcharts, or external PostgreSQL/MySQL/MariaDB.
- HA-ready controls — JDBC_PING cache stack, multi-replica scheduling defaults, PDB, rollout strategy, and capacity profiles.
- Routing options — separate public/admin Ingress resources plus optional Gateway API
HTTPRouteresources. - Security integration — non-root defaults, configurable ServiceAccount token mount, truststore controls, NetworkPolicy ingress and egress, and External Secrets Operator opt-in.
- Observability — health probes, management service, metrics endpoint, ServiceMonitor, telemetry, tracing, and structured logging controls.
- Backup — database-only PostgreSQL/MySQL dump CronJob uploaded to S3-compatible storage.
Architecture
Production
Public traffic reaches the Keycloak application service through Ingress or Gateway API. Health and metrics stay on the private management service.
High Availability
Multiple Keycloak pods share the same database, use JDBC_PING for cache discovery, and can expose optional backup and External Secrets flows.
Installation
HTTPS repository:
helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install keycloak helmforge/keycloak -f values.yaml
OCI registry:
helm install keycloak oci://ghcr.io/helmforgedev/helm/keycloak -f values.yaml
mode: production fails fast unless hostname.hostname is set and one database path is configured through
postgresql.enabled, mysql.enabled, or database.external.host.
Deployment Examples
# values.yaml - dev mode with embedded H2
mode: dev
admin:
username: admin
password: 'change-me'Dev mode is useful for local validation only. Use PostgreSQL, MySQL, MariaDB, or the PostgreSQL/MySQL subchart for production.
# values.yaml - production with external PostgreSQL
mode: production
hostname:
hostname: https://sso.example.com
admin:
existingSecret: keycloak-admin
database:
external:
vendor: postgres
host: postgresql.database.svc.cluster.local
port: 5432
name: keycloak
username: keycloak
existingSecret: keycloak-db
existingSecretPasswordKey: db-password
pool:
initialSize: 5
minSize: 5
maxSize: 30
logSlowQueriesThreshold: 500ms# values.yaml - HA with PostgreSQL subchart
mode: production
replicaCount: 3
hostname:
hostname: https://sso.example.com
postgresql:
enabled: true
auth:
database: keycloak
username: keycloak
password: 'strong-db-password'
cache:
enabled: true
stack: jdbc-ping
capacity:
profile: medium
pdb:
enabled: true
minAvailable: 1# values.yaml - public and admin ingress
mode: production
hostname:
hostname: https://sso.example.com
admin: https://admin-sso.example.com
database:
external:
host: postgresql.database.svc.cluster.local
password: 'db-password'
proxy:
headers: xforwarded
trustedAddresses: 10.0.0.0/8,192.168.0.0/16
ingress:
public:
enabled: true
ingressClassName: traefik
hosts:
- host: sso.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: keycloak-tls
hosts:
- sso.example.com
admin:
enabled: true
ingressClassName: traefik
hosts:
- host: admin-sso.example.com
paths:
- path: /
pathType: Prefix# values.yaml - metrics, logs, backup, and network policy
mode: production
hostname:
hostname: https://sso.example.com
postgresql:
enabled: true
auth:
database: keycloak
username: keycloak
password: 'strong-db-password'
metrics:
enabled: true
userEvents: true
serviceMonitor:
enabled: true
interval: 30s
logging:
console:
output: json
jsonFormat: ecs
access:
enabled: true
backup:
enabled: true
schedule: '0 3 * * *'
s3:
endpoint: https://s3.example.com
bucket: keycloak-backups
existingSecret: keycloak-s3
networkPolicy:
enabled: true
egress:
enabled: true
databaseTo:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: database# values.yaml - Gateway API and External Secrets Operator
mode: production
hostname:
hostname: https://sso.example.com
database:
external:
host: postgresql.database.svc.cluster.local
existingSecret: keycloak-db
gateway:
public:
enabled: true
parentRefs:
- name: public-gateway
namespace: gateway-system
hostnames:
- sso.example.com
externalSecrets:
enabled: true
secretStoreRef:
name: platform-secrets
kind: ClusterSecretStore
admin:
enabled: true
usernameRemoteRef:
key: keycloak/admin
property: username
passwordRemoteRef:
key: keycloak/admin
property: password
database:
enabled: true
passwordRemoteRef:
key: keycloak/database
property: passwordConfiguration Reference
Core and Image
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | dev | Runtime mode: dev or production. |
nameOverride | string | "" | Override chart name. |
fullnameOverride | string | "" | Override full release name. |
commonLabels | object | {} | Labels added to all resources. |
clusterDomain | string | cluster.local | Kubernetes cluster DNS domain. |
image.repository | string | quay.io/keycloak/keycloak | Keycloak image repository. |
image.tag | string | "26.6.1" | Keycloak image tag. |
image.pullPolicy | string | IfNotPresent | Image pull policy. |
imagePullSecrets | array | [] | Pull secrets for private registries. |
Bootstrap Admin
| Parameter | Type | Default | Description |
|---|---|---|---|
admin.username | string | admin | Bootstrap admin username. |
admin.password | string | "" | Bootstrap admin password. Generated when empty and no existing secret is set. |
admin.existingSecret | string | "" | Existing Kubernetes Secret for bootstrap admin credentials. |
admin.existingSecretUsernameKey | string | admin-username | Username key in the admin secret. |
admin.existingSecretPasswordKey | string | admin-password | Password key in the admin secret. |
HTTP, Management, Hostname, and Proxy
| Parameter | Type | Default | Description |
|---|---|---|---|
http.enabled | boolean | true | Keep HTTP enabled inside the cluster. |
http.port | integer | 8080 | Application HTTP port. |
http.managementPort | integer | 9000 | Management interface port for health and metrics. |
http.relativePath | string | / | Application relative path, for example /auth. |
management.healthEnabled | boolean | true | Serve health endpoints from the management interface. |
management.relativePath | string | "" | Optional management interface relative path. |
hostname.hostname | string | "" | Public Keycloak hostname or URL. Required in production. |
hostname.admin | string | "" | Dedicated admin hostname or URL. |
hostname.strict | boolean | true | Enforce strict hostname handling. |
hostname.backchannelDynamic | boolean | false | Enable dynamic backchannel URL resolution. |
proxy.headers | string | xforwarded | Proxy header mode: xforwarded, forwarded, or empty. |
proxy.trustedAddresses | string | "" | Trusted proxy IPs/CIDRs for KC_PROXY_TRUSTED_ADDRESSES. |
proxy.protocolEnabled | boolean | false | Enable HAProxy PROXY protocol. Cannot be combined with proxy.headers. |
Database
| Parameter | Type | Default | Description |
|---|---|---|---|
database.external.vendor | string | postgres | External database vendor: postgres, mysql, or mariadb. |
database.external.host | string | "" | External database host. |
database.external.port | string/integer | "" | External database port. Auto-detected from vendor when empty. |
database.external.name | string | keycloak | External database name. |
database.external.username | string | keycloak | External database username. |
database.external.password | string | "" | External database password. Generated when empty and no existing secret is set. |
database.external.existingSecret | string | "" | Existing Secret containing database password. |
database.external.existingSecretPasswordKey | string | db-password | Password key in the existing Secret. |
database.external.jdbcParameters | string | "" | Additional JDBC URL parameters. |
database.external.schema | string | "" | Optional database schema passed to KC_DB_SCHEMA. |
database.external.pool.initialSize | string/integer | "" | Initial DB connection pool size. |
database.external.pool.minSize | string/integer | "" | Minimum DB connection pool size. |
database.external.pool.maxSize | string/integer | "" | Maximum DB connection pool size. |
database.external.pool.maxLifetime | string | "" | Maximum DB connection lifetime. |
database.external.logSlowQueriesThreshold | string | "" | Slow query log threshold. |
database.external.transaction.xaEnabled | string/boolean | "" | Enable or disable XA transactions. |
database.external.transaction.timeout | string | "" | Transaction timeout. |
postgresql.enabled | boolean | false | Enable the bundled PostgreSQL chart. |
postgresql.auth.database | string | keycloak | PostgreSQL database name. |
postgresql.auth.username | string | keycloak | PostgreSQL username. |
postgresql.auth.password | string | "" | PostgreSQL password. Required for deterministic subchart connections. |
mysql.enabled | boolean | false | Enable the bundled MySQL chart. |
mysql.auth.database | string | keycloak | MySQL database name. |
mysql.auth.username | string | keycloak | MySQL username. |
mysql.auth.password | string | "" | MySQL password. Required for deterministic subchart connections. |
Database TLS and Truststore
| Parameter | Type | Default | Description |
|---|---|---|---|
database.tls.enabled | boolean | false | Enable database TLS settings. |
database.tls.mode | string | "" | Keycloak database TLS mode. |
database.tls.sslMode | string | verify-full | PostgreSQL SSL mode. |
database.tls.mountPath | string | /opt/keycloak/conf/db-certs | Mount path for DB TLS material. |
database.tls.rootCertFilename | string | ca.crt | Root CA filename. |
database.tls.existingSecret | string | "" | Secret holding DB CA material. |
database.tls.existingConfigMap | string | "" | ConfigMap holding DB CA material. |
database.tls.trustStoreFile | string | "" | DB TLS truststore file path. |
database.tls.trustStorePasswordSecret | string | "" | Secret containing DB TLS truststore password. |
database.tls.trustStorePasswordKey | string | password | Password key for DB TLS truststore. |
database.tls.trustStoreType | string | "" | DB TLS truststore type. |
truststore.enabled | boolean | false | Enable additional outbound TLS trust material. |
truststore.mountPath | string | /opt/keycloak/conf/truststores/custom | Custom truststore mount path. |
truststore.existingSecret | string | "" | Secret with PEM or unencrypted PKCS12 trust material. |
truststore.existingConfigMap | string | "" | ConfigMap with PEM or unencrypted PKCS12 trust material. |
truststore.tlsHostnameVerifier | string | DEFAULT | Outbound TLS hostname verifier mode. |
truststore.kubernetes.enabled | boolean | true | Let Keycloak trust Kubernetes/OpenShift service account CA files when mounted. |
Runtime, HA, and Capacity
| Parameter | Type | Default | Description |
|---|---|---|---|
replicaCount | integer | 1 | Number of Keycloak replicas. |
cache.enabled | boolean | true | Enable distributed cache settings in production mode. |
cache.stack | string | jdbc-ping | Cache stack used for multi-replica production deployments. |
cache.multiReplicaDefaults.enabled | boolean | true | Apply scheduling defaults for multi-replica deployments. |
cache.multiReplicaDefaults.podAntiAffinity | string | preferred | Automatic anti-affinity mode: preferred, required, or none. |
cache.multiReplicaDefaults.topologySpread.enabled | boolean | true | Apply automatic topology spread for multi-replica deployments. |
features.enabled | array | [] | Keycloak features rendered as KC_FEATURES. |
features.disabled | array | [] | Keycloak features rendered as KC_FEATURES_DISABLED. |
optimized.enabled | boolean | false | Add --optimized; use only with pre-built/custom optimized images in production. |
deployment.strategy.type | string | RollingUpdate | Deployment strategy: RollingUpdate or Recreate. |
deployment.strategy.rollingUpdate.maxUnavailable | string/integer | 0 | Maximum unavailable pods during rolling update. |
deployment.strategy.rollingUpdate.maxSurge | string/integer | 1 | Maximum surge pods during rolling update. |
resources | object | {} | Custom container resources when capacity.profile=custom. |
capacity.profile | string | custom | Resource profile: custom, small, medium, or large. |
capacity.profiles | object | built-in profiles | Profile definitions for small, medium, and large. |
Realm Import, Providers, and Themes
| Parameter | Type | Default | Description |
|---|---|---|---|
realmImport.enabled | boolean | false | Enable startup realm import using --import-realm. |
realmImport.files | object | {} | Inline realm JSON files written to a generated ConfigMap. |
realmImport.existingConfigMap | string | "" | Existing ConfigMap mounted into /opt/keycloak/data/import. |
extensions.providers.existingConfigMap | string | "" | ConfigMap mounted into /opt/keycloak/providers. |
extensions.providers.existingSecret | string | "" | Secret mounted into /opt/keycloak/providers. |
extensions.providers.rolloutToken | string | "" | Manual rollout token for provider changes. |
extensions.themes.existingConfigMap | string | "" | ConfigMap mounted into /opt/keycloak/themes. |
extensions.themes.existingSecret | string | "" | Secret mounted into /opt/keycloak/themes. |
extensions.themes.rolloutToken | string | "" | Manual rollout token for theme changes. |
Services, Ingress, and Gateway API
| Parameter | Type | Default | Description |
|---|---|---|---|
service.type | string | ClusterIP | Application Service type. |
service.annotations | object | {} | Application Service annotations. |
service.managementAnnotations | object | {} | Management Service annotations. |
service.ipFamilyPolicy | string | "" | Application Service IP family policy. |
service.ipFamilies | array | [] | Application Service IP families. |
service.managementIpFamilyPolicy | string | "" | Management Service IP family policy. |
service.managementIpFamilies | array | [] | Management Service IP families. |
ingress.public.enabled | boolean | false | Create public Ingress. |
ingress.public.ingressClassName | string | traefik | Public Ingress class. |
ingress.public.hosts | array | [] | Public host/path rules. |
ingress.public.tls | array | [] | Public TLS configuration. |
ingress.admin.enabled | boolean | false | Create admin Ingress. |
ingress.admin.ingressClassName | string | traefik | Admin Ingress class. |
ingress.admin.hosts | array | [] | Admin host/path rules. |
ingress.admin.tls | array | [] | Admin TLS configuration. |
gateway.public.enabled | boolean | false | Create public Gateway API HTTPRoute. |
gateway.public.parentRefs | array | [] | Required parentRefs when public Gateway is enabled. |
gateway.public.hostnames | array | [] | Public route hostnames. |
gateway.admin.enabled | boolean | false | Create admin Gateway API HTTPRoute. |
gateway.admin.parentRefs | array | [] | Required parentRefs when admin Gateway is enabled. |
gateway.admin.hostnames | array | [] | Admin route hostnames. |
Health, Metrics, Telemetry, and Logs
| Parameter | Type | Default | Description |
|---|---|---|---|
health.enabled | boolean | true | Enable Keycloak health endpoints. |
probes.profile | string | default | Probe profile: default or heavy-startup. |
probes.liveness.enabled | boolean | true | Enable liveness probe. |
probes.readiness.enabled | boolean | true | Enable readiness probe. |
probes.startup.enabled | boolean | true | Enable startup probe. |
metrics.enabled | boolean | false | Enable Keycloak metrics endpoint. |
metrics.userEvents | boolean | false | Enable user event metrics. |
metrics.cacheHistograms | boolean | false | Enable cache metrics histograms. |
metrics.serviceMonitor.enabled | boolean | false | Create a ServiceMonitor targeting the management service. |
metrics.serviceMonitor.interval | string | 30s | ServiceMonitor scrape interval. |
metrics.serviceMonitor.scrapeTimeout | string | "" | Optional scrape timeout. |
metrics.serviceMonitor.relabelings | array | [] | Prometheus relabeling rules. |
metrics.serviceMonitor.metricRelabelings | array | [] | Prometheus metric relabeling rules. |
telemetry.metricsEnabled | boolean | false | Enable OpenTelemetry metrics export. |
telemetry.endpoint | string | "" | General OpenTelemetry endpoint. |
telemetry.metricsEndpoint | string | "" | Metrics-specific OpenTelemetry endpoint. |
tracing.enabled | boolean | false | Enable OpenTelemetry tracing. |
tracing.endpoint | string | "" | Tracing endpoint. |
tracing.samplerType | string | "" | Tracing sampler type. |
tracing.samplerRatio | string/number | "" | Tracing sampler ratio. |
logging.level | string | info | Global Keycloak log level. |
logging.console.output | string | default | Console output mode: default or json. |
logging.console.jsonFormat | string | default | JSON log format: default or ecs. |
logging.access.enabled | boolean | false | Enable HTTP access logs. |
Backup
| Parameter | Type | Default | Description |
|---|---|---|---|
backup.enabled | boolean | false | Enable database backup CronJob. |
backup.schedule | string | "0 3 * * *" | Backup schedule. |
backup.suspend | boolean | false | Suspend scheduled execution. |
backup.concurrencyPolicy | string | Forbid | CronJob concurrency policy. |
backup.archivePrefix | string | keycloak | Prefix for generated archive names. |
backup.images.uploader.repository | string | docker.io/helmforge/mc | S3 uploader image. |
backup.images.uploader.tag | string | "1.0.0" | S3 uploader image tag. |
backup.s3.endpoint | string | "" | S3-compatible endpoint URL. |
backup.s3.bucket | string | "" | Target bucket name. |
backup.s3.prefix | string | keycloak | Object key prefix. |
backup.s3.createBucketIfNotExists | boolean | true | Create bucket when missing. |
backup.s3.existingSecret | string | "" | Existing Secret with S3 credentials. |
backup.database.postgresDumpArgs | string | --clean --if-exists | Extra pg_dump arguments. |
backup.database.mysqlDumpArgs | string | --single-transaction --quick --skip-lock-tables --no-tablespaces | Extra mysqldump arguments. |
Security, Scheduling, and Extension Points
| Parameter | Type | Default | Description |
|---|---|---|---|
pdb.enabled | boolean | false | Create PodDisruptionBudget. |
pdb.minAvailable | integer | 1 | Minimum available pods. |
pdb.maxUnavailable | string | "" | Maximum unavailable pods. |
networkPolicy.enabled | boolean | false | Create NetworkPolicy for Keycloak pods. |
networkPolicy.ingress.allowSameNamespace | boolean | true | Allow app traffic from the same namespace. |
networkPolicy.management.allowSameNamespace | boolean | true | Allow management traffic from the same namespace. |
networkPolicy.clustering.enabled | boolean | true | Allow Keycloak pod-to-pod cache transport when multi-replica. |
networkPolicy.egress.enabled | boolean | false | Add egress rules. |
networkPolicy.egress.allowDns | boolean | true | Allow TCP/UDP DNS egress. |
networkPolicy.egress.allowDatabase | boolean | true | Allow DB egress when databaseTo is configured. |
networkPolicy.egress.databaseTo | array | [] | Egress destination peers for database traffic. |
networkPolicy.egress.extraEgress | array | [] | Additional egress rules. |
serviceAccount.create | boolean | false | Create a ServiceAccount. |
serviceAccount.name | string | "" | ServiceAccount name override. |
serviceAccount.automountServiceAccountToken | boolean | true | Mount the Kubernetes ServiceAccount token and CA into the pod. |
podSecurityContext | object | non-root defaults | Pod-level security context. |
securityContext | object | non-root defaults | Container-level security context. |
nodeSelector | object | {} | Node selector. |
tolerations | array | [] | Pod tolerations. |
affinity | object | {} | Custom affinity. |
topologySpreadConstraints | array | [] | Custom topology spread constraints. |
priorityClassName | string | "" | PriorityClass name. |
terminationGracePeriodSeconds | integer | 120 | Pod termination grace period. |
extraEnv | array | [] | Extra environment variables. |
extraEnvFrom | array | [] | Extra envFrom sources. |
initContainers | array | [] | Extra init containers. |
extraContainers | array | [] | Extra sidecars. |
extraVolumes | array | [] | Extra pod volumes. |
extraVolumeMounts | array | [] | Extra Keycloak volume mounts. |
External Secrets Operator
| Parameter | Type | Default | Description |
|---|---|---|---|
externalSecrets.enabled | boolean | false | Render ExternalSecret resources for clusters that already run External Secrets Operator. |
externalSecrets.apiVersion | string | external-secrets.io/v1 | ExternalSecret API version. |
externalSecrets.refreshInterval | string | 1h | Refresh interval. |
externalSecrets.secretStoreRef.name | string | "" | Existing SecretStore or ClusterSecretStore name. Required when enabled. |
externalSecrets.secretStoreRef.kind | string | SecretStore | Store kind: SecretStore or ClusterSecretStore. |
externalSecrets.admin.enabled | boolean | false | Materialize bootstrap admin Secret through ESO. |
externalSecrets.database.enabled | boolean | false | Materialize database password Secret through ESO. |
externalSecrets.truststore.enabled | boolean | false | Materialize truststore Secret through ESO. |
The chart does not install External Secrets Operator and does not create SecretStore or ClusterSecretStore
resources. Enable this only on clusters where the operator and store already exist.
Operational Notes
Chart.yamlpins PostgreSQL chart1.9.13and MySQL chart1.8.7.Chart.lockis versioned for reproducible dependency builds.- Built-in backup is database-only. Back up providers, themes, realm import files, truststores, and external secret backends through their own source-of-truth.
- The management service is private and should not be exposed through public ingress.
- Gateway API routes target the application service only; they do not route the management service.
- Dual-stack fields are optional and omitted by default so the cluster default remains authoritative.
serviceAccount.automountServiceAccountToken=falsecan harden the pod, but it also removes the default Kubernetes CA material used by Keycloak truststore auto-discovery.