Back to blog
October 2025 6 min read

Keycloak Demystified: One Login to Rule Every App

An introduction to HashiCorp-grade identity management with Keycloak — core concepts, architecture, and a practical guide to deploying it with Docker and Kubernetes, including persistent themes/providers and MySQL-backed production setups.

Keycloak IAM SSO Kubernetes Docker DevOps Security

If you've ever built a login system from scratch — password hashing, session tokens, "forgot password" flows, social login buttons — you know it's a rabbit hole. Keycloak exists so you never have to go down it again.


What is Keycloak?

Keycloak is an open-source Identity and Access Management (IAM) platform, originally built by Red Hat (now a CNCF project). It handles authentication, authorization, and single sign-on (SSO) for web and mobile applications, so development teams don't have to reinvent user management for every app they ship.

Core Capabilities

| Feature | What it does |

Single Sign-On (SSO) | Log in once, access every connected app — no repeated credential prompts.

Authentication | Username/password, social logins (Google, Facebook), MFA, OpenID Connect, SAML.

Authorization | Fine-grained, role- and attribute-based access control per resource.

User Federation | Syncs with LDAP, Active Directory, or custom user stores.

Identity Brokering | Lets users log in via existing social or enterprise accounts.

Self-Service | Users manage their own profiles and password resets.

Security Hardening | Session management, token validation, brute-force protection.

Extensibility | Fully open source and customizable for enterprise needs.

Because it speaks standard protocols (OpenID Connect, SAML, OAuth2), Keycloak drops into almost any stack — regardless of language or framework — as a centralized identity layer.


Key Concepts

Before deploying, it helps to know the vocabulary:

  • Realm — An isolated security domain. Each realm has its own users, groups, roles, and clients, so you can separate tenants or environments cleanly.
  • Clients — The applications or services that authenticate against Keycloak (web apps, mobile apps, APIs).
  • Client Scopes — Named sets of permissions/attributes a client can request during login (e.g., an email scope).
  • Roles — Fine-grained permissions assigned to users or clients, defining what they're allowed to do.
  • Identity Providers — External login sources (Google, GitHub, corporate SSO) federated into a realm.
  • User Federation — Bridges to external directories like LDAP or Active Directory.
  • Authentication Flows — Configurable login pipelines supporting MFA and custom logic.

Architecture at a Glance

User → Application (Client) → Keycloak Realm → Auth Method (local / LDAP / social IdP)
                                     ↓
                         Token issued (OIDC / SAML)
                                     ↓
                    Application validates token → Access granted

Keycloak sits in front of your applications as a dedicated auth layer — apps redirect to Keycloak for login, and get back a signed token they can trust.


Deployment: Docker (Quick Start)

For local testing or demos, a single container gets you running in seconds:

docker run -d \
  --name keycloak \
  -p 8080:8080 \
  -e KEYCLOAK_ADMIN=admin \
  -e KEYCLOAK_ADMIN_PASSWORD=12345Q \
  quay.io/keycloak/keycloak:latest start-dev

start-dev runs Keycloak in development mode — fine for experimenting, not for production (no HTTPS enforcement, in-memory-friendly defaults).


Deployment: Kubernetes (Dev Mode)

For a quick in-cluster deployment, define a Deployment and expose it via NodePort.

keycloak-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: keycloak
  labels:
    app: keycloak
spec:
  replicas: 1
  selector:
    matchLabels:
      app: keycloak
  template:
    metadata:
      labels:
        app: keycloak
    spec:
      containers:
        - name: keycloak
          image: quay.io/keycloak/keycloak:22.0.3
          args: ["start-dev"]
          env:
            - name: KEYCLOAK_ADMIN
              value: "admin"
            - name: KEYCLOAK_ADMIN_PASSWORD
              value: "admin"
            - name: KC_PROXY
              value: "edge"
          ports:
            - name: http
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /realms/master
              port: 8080

keycloak-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: keycloak
  labels:
    app: keycloak
spec:
  ports:
    - name: http
      port: 8080
      targetPort: 8080
      nodePort: 31503
  selector:
    app: keycloak
  type: NodePort
kubectl apply -f keycloak-deployment.yaml
kubectl apply -f keycloak-service.yaml

Or skip the YAML entirely and apply the official quickstart directly:

kubectl create -f https://raw.githubusercontent.com/keycloak/keycloak-quickstarts/latest/kubernetes/keycloak.yaml

Production-Grade Kubernetes Deployment

A production setup needs three things dev mode skips: persistent storage for themes/providers, a real database instead of embedded storage, and proper hostname/proxy configuration.

1. Persistent Volumes for Themes and Providers

Custom themes and provider JARs shouldn't vanish on pod restart. Provision dedicated PVs/PVCs for each:

keycloak-pv-providers.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: keycloak-pv-providers
  labels:
    type: local
spec:
  storageClassName: local-storage-class
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/app/data/keycloak/providers"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: keycloak-pvc-providers
spec:
  storageClassName: local-storage-class
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

keycloak-pv-themes.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: keycloak-pv
  labels:
    type: local
spec:
  storageClassName: local-storage-class
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/app/data/keycloak/themes"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: keycloak-pv-claim
spec:
  storageClassName: local-storage-class
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

2. Deployment with MySQL Backend and Reverse Proxy Support

This is the setup you'd actually run behind an ingress/reverse proxy, with a real hostname and a MySQL-backed realm store — plus Prometheus metrics enabled.

keycloak-deploy-all.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: keycloak
spec:
  selector:
    matchLabels:
      app: keycloak
  replicas: 1
  template:
    metadata:
      annotations:
        kubernetes.io/change-cause: customize keycloak of quay.io/keycloak/keycloak:25.0.2 for enable monitoring
      labels:
        app: keycloak
    spec:
      volumes:
        - name: keycloak-pv
          persistentVolumeClaim:
            claimName: keycloak-pv-claim
        - name: keycloak-pv-providers
          persistentVolumeClaim:
            claimName: keycloak-pvc-providers
      containers:
      - name: keycloak
        image: quay.io/keycloak/keycloak:latest
        args: ["start", "--metrics-enabled=true"]
        imagePullPolicy: IfNotPresent
        volumeMounts:
          - mountPath: "/opt/keycloak/themes"
            name: keycloak-pv
          - mountPath: "/opt/keycloak/providers"
            name: keycloak-pv-providers
        env:
        - name: KEYCLOAK_ADMIN
          value: 'admin'
        - name: KEYCLOAK_ADMIN_PASSWORD
          value: '12345Q'
        - name: PROXY_ADDRESS_FORWARDING
          value: 'true'
        - name: KC_HOSTNAME
          value: 'your-domain.example.com'
        - name: KC_HTTP_RELATIVE_PATH
          value: '/idp'
        - name: KC_HOSTNAME_ADMIN_URL
          value: 'https://your-domain.example.com/idp'
        - name: KC_HTTP_HOST
          value: '0.0.0.0'
        - name: KC_PROXY
          value: 'edge'
        - name: KC_HOSTNAME_STRICT
          value: 'false'
        - name: KC_DB
          value: 'mysql'
        - name: KC_DB_URL
          value: 'jdbc:mysql://<db-host>:3306/keycloak'
        - name: KC_DB_USERNAME
          value: 'root'
        - name: KC_DB_PASSWORD
          value: '<db-password>'
        ports:
          - name: idp-cport
            containerPort: 8080
          - name: idp-mport
            containerPort: 9000
      restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: keycloak
spec:
  selector:
    app: keycloak
  ports:
    - name: http
      port: 8080
      targetPort: 8080
      nodePort: 30500
    - name: management-port
      port: 9000
      targetPort: 9000
      nodePort: 30501
  type: NodePort
kubectl apply -f keycloak-pv-providers.yaml
kubectl apply -f keycloak-pv-themes.yaml
kubectl apply -f keycloak-deploy-all.yaml

Note: --metrics-enabled=true exposes Prometheus-scrapable metrics on the management port (9000), useful for wiring into a Grafana dashboard alongside the rest of your platform observability stack. Replace database credentials and hostnames with values pulled from Vault/ESO rather than hardcoding them in the manifest.


Where Keycloak Fits in a GitOps Platform

For teams already running ArgoCD, Vault/ESO, and Helm, Keycloak slots in naturally:

  • Package the production manifests above into a Helm chart, templating hostname, DB credentials, and replica count.
  • Store DB credentials and admin passwords in Vault, synced via ExternalSecrets rather than plaintext env vars.
  • Let ArgoCD manage the Keycloak Application, so realm/theme/provider changes ship through the same GitOps pipeline as everything else.
  • Front it with Traefik (Gateway API / HTTPRoute) for TLS termination and routing, matching the KC_PROXY=edge configuration.

Summary

| Component | Role | |---|---| | Realm | Isolated tenant/security boundary | | Client | Application registered to authenticate via Keycloak | | Identity Provider | External login source (social/enterprise) | | User Federation | Bridge to LDAP/AD user stores | | PV/PVC (themes, providers) | Persist customizations across restarts | | MySQL Backend | Durable storage for realms, users, and configuration |

Auth flow: User → App redirects to Keycloak → Keycloak authenticates → Token issued → App grants access