How to Deploy Keycloak on Kubernetes for SSO
Deploy Keycloak on Kubernetes as a centralized identity provider for single sign-on, authentication, and application access management.
Published · Republished on Medium

What this deployment must solve
Running Keycloak on Kubernetes is more than adding replicas behind an Ingress. A production design needs a durable external database, a stable public hostname, a clearly defined TLS boundary, protected bootstrap credentials, observable health endpoints, and a tested upgrade and rollback path.
This guide uses the current Quarkus-based Keycloak distribution. Exact Helm values differ between chart versions, so pin both the chart and Keycloak image and verify the values exposed by that release before deployment.
Recommended architecture
Use these components as separate failure domains:
- At least two Keycloak pods spread across nodes or availability zones.
- A managed PostgreSQL service with backups and point-in-time recovery.
- An Ingress or load balancer that terminates TLS and forwards only trusted proxy headers.
- Kubernetes Secrets or an external secret manager for database and bootstrap credentials.
- Metrics and health endpoints exposed only to internal monitoring systems.
Multiple replicas improve availability, but they do not create a specific SLA by themselves. Availability also depends on the database, load balancer, DNS, cluster capacity, disruption policy, and operational response.
Step 1: Prepare PostgreSQL safely
Create a dedicated database and a least-privilege login. Do not copy generic PostgreSQL tuning values such as shared_buffers or work_mem into production without measuring the database workload and available memory.
sql
CREATE ROLE keycloak LOGIN PASSWORD 'replace-through-a-secret-manager';
CREATE DATABASE keycloak_db OWNER keycloak ENCODING 'UTF8';
REVOKE ALL ON DATABASE keycloak_db FROM PUBLIC;
GRANT CONNECT, TEMPORARY ON DATABASE keycloak_db TO keycloak;Store the real password outside Git and inject it into the workload from a Secret. Enable encrypted connections to PostgreSQL, automated backups, restore testing, and monitoring for connection saturation and storage growth.
Step 2: Build an optimized Keycloak image
Keycloak recommends running the build phase before startup for faster and more predictable container starts. Pin a tested version instead of using latest.
dockerfile
ARG KEYCLOAK_VERSION=26.7.0
FROM quay.io/keycloak/keycloak:${KEYCLOAK_VERSION} AS builder
ENV KC_HEALTH_ENABLED=true
ENV KC_METRICS_ENABLED=true
ENV KC_DB=postgres
RUN /opt/keycloak/bin/kc.sh build
FROM quay.io/keycloak/keycloak:${KEYCLOAK_VERSION}
COPY --from=builder /opt/keycloak/ /opt/keycloak/
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
CMD ["start", "--optimized"]Treat the version above as an example pin, not an instruction to upgrade blindly. Scan the resulting image, test extensions and themes, and promote the same digest between environments.
Step 3: Create credentials without committing them
The following command illustrates the required keys. Prefer External Secrets, Secrets Store CSI Driver, or an equivalent system when your platform already has a secret manager.
bash
kubectl -n keycloak create secret generic keycloak-runtime \
--from-literal=KC_DB_USERNAME=keycloak \
--from-literal=KC_DB_PASSWORD='replace-me' \
--from-literal=KC_BOOTSTRAP_ADMIN_USERNAME=bootstrap-admin \
--from-literal=KC_BOOTSTRAP_ADMIN_PASSWORD='replace-me'Use bootstrap administrator credentials only to establish durable administrative access. Rotate or remove them after validating the permanent administrator and emergency access procedure.
Step 4: Configure hostname, proxy, and TLS explicitly
Keycloak uses its hostname settings when generating issuer URLs, redirects, cookies, and discovery metadata. Set the public HTTPS URL explicitly and keep strict hostname checking enabled.
When TLS terminates at a trusted reverse proxy:
yaml
env:
- name: KC_HOSTNAME
value: "https://keycloak.example.com"
- name: KC_HTTP_ENABLED
value: "true"
- name: KC_PROXY_HEADERS
value: "xforwarded"
- name: KC_DB
value: "postgres"
- name: KC_DB_URL
value: "jdbc:postgresql://postgres.example.internal:5432/keycloak_db"
envFrom:
- secretRef:
name: keycloak-runtimeOnly enable KC_PROXY_HEADERS when untrusted clients cannot reach the Keycloak service directly and the proxy overwrites the corresponding forwarded headers. Otherwise, forged headers can affect origin checks and generated URLs. Keep the internal HTTP listener private through NetworkPolicy and security-group rules.
If Keycloak terminates TLS itself, mount the certificate and key and do not enable plain HTTP. Do not mix both models unintentionally.
Step 5: Pin and inspect the Helm chart
The chart-specific keys must match the exact chart release. Inspect them before writing the production values file:
bash
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo bitnami/keycloak --versions
helm show values bitnami/keycloak --version <PINNED_CHART_VERSION> > keycloak-default-values.yamlYour reviewed values should cover at least:
- A pinned image repository, tag, and preferably digest.
- External PostgreSQL with credentials referenced from an existing Secret.
- Two or more replicas with pod anti-affinity or topology spread constraints.
- CPU and memory requests established from load testing.
- A PodDisruptionBudget that still permits planned node maintenance.
- Startup, readiness, and liveness probes that use Keycloak's health endpoints.
- A private management interface for health and metrics, normally port
9000. - Ingress TLS, hostname, and backend protocol aligned with the chosen TLS boundary.
Render and review the manifests before applying them:
bash
helm template keycloak bitnami/keycloak \
--namespace keycloak \
--version <PINNED_CHART_VERSION> \
--values keycloak-values.yaml > rendered-keycloak.yaml
helm upgrade --install keycloak bitnami/keycloak \
--namespace keycloak \
--create-namespace \
--version <PINNED_CHART_VERSION> \
--values keycloak-values.yaml \
--atomic \
--timeout 15mNever place shell placeholders such as ${ADMIN_PASSWORD} in a values file and assume Helm will resolve them automatically. Reference an existing Secret through supported chart values or render secrets in a controlled deployment step.
Step 6: Design clustering and disruption controls
Keycloak uses distributed caches for clustered operation. Confirm the cache discovery mechanism supported by the selected chart and Keycloak version. Permit only the required pod-to-pod traffic, and verify cluster membership after every rollout.
A three-replica deployment can still become unavailable if all pods share one node, the database is unreachable, or a strict disruption budget blocks recovery. Combine topology spread, adequate spare cluster capacity, graceful termination, and conservative rollout settings.
Horizontal Pod Autoscaling is optional. Add it only after load tests identify useful CPU, memory, or application metrics. Keycloak startup, cache rebalancing, database connection limits, and login-session behavior make aggressive scaling counterproductive.
Step 7: Validate the deployment
Check the entire authentication path rather than relying only on pod status:
bash
kubectl -n keycloak get pods,service,ingress
kubectl -n keycloak rollout status statefulset/keycloak --timeout=10m
curl --fail --silent https://keycloak.example.com/realms/master/.well-known/openid-configurationAdapt the workload name if the chart renders a Deployment. Also verify:
- The discovery document reports the expected HTTPS issuer.
- Browser login, logout, refresh-token, and callback flows work through the public hostname.
- Readiness fails when the database is unavailable and recovers after service restoration.
- Metrics and health endpoints are unreachable from the public Internet.
- Removing one pod and draining one node does not interrupt the tested authentication flow.
- Audit events reach the approved log destination without exposing tokens or passwords.
Upgrade, backup, and rollback
Back up PostgreSQL and test restoration before a Keycloak upgrade. Read the Keycloak migration notes, test realm configuration, providers, themes, and clients against a copy of production data, then deploy gradually. Database schema changes can make an application-image rollback insufficient, so document the compatible rollback boundary for each release.
Exporting realms is useful for configuration portability, but it is not a substitute for database backups. Keep tested database recovery procedures and retain the previous container digest and rendered manifests.
Production checklist
- Image and Helm chart versions are pinned and scanned.
- Public hostname, TLS termination, and proxy-header trust are explicit.
- Runtime and bootstrap credentials are outside Git and rotated.
- PostgreSQL encryption, backup, restore, and capacity controls are tested.
- Pods are distributed and disruption behavior is tested.
- Health and metrics endpoints are private and monitored.
- Login, logout, token refresh, and failure scenarios pass before release.
- Upgrade and database-compatible rollback steps are documented.
Further reading
- Keycloak container guide
- Keycloak hostname configuration
- Keycloak reverse proxy guidance
- Keycloak production configuration
- Keycloak high-availability guide
- Kubernetes Secrets good practices
Conclusion
A reliable Keycloak deployment is defined by controlled dependencies and tested failure behavior, not by replica count alone. Pin the software, keep credentials out of source control, make the proxy and hostname contract explicit, protect the management endpoints, and prove database recovery and upgrade safety before calling the service production-ready.