Keycloak Helm vs Operator: Which to Use on Kubernetes

Guilliano Molaire Guilliano Molaire Updated May 30, 2026 8 min read

Last updated: March 2026

Deploying Keycloak on Kubernetes gives you the scalability and orchestration that production identity services demand. But Kubernetes offers two distinct deployment models for Keycloak: the Bitnami Helm chart (a traditional package manager approach) and the Keycloak Operator (a Kubernetes-native, CRD-driven approach). Each has tradeoffs in complexity, flexibility, and operational overhead.

This guide compares both approaches with real configuration examples, production-ready patterns, and a decision framework for choosing the right one for your team.

Quick Comparison

Aspect Bitnami Helm Chart Keycloak Operator
Deployment model Helm release Custom Resources (CRDs)
Configuration values.yaml Keycloak CR + KeycloakRealmImport CR
Realm management Manual or external tooling CRD-based (KeycloakRealmImport)
Auto-scaling HPA (manual setup) Built-in support
Certificate management External (cert-manager) Integrated
Database provisioning Configurable in values Separate (you manage)
Upgrade strategy helm upgrade Operator-managed rolling update
Learning curve Lower (Helm familiarity) Higher (CRD concepts)
Community Bitnami / VMware Keycloak project (CNCF)

Approach 1: Bitnami Helm Chart

The Bitnami Keycloak Helm chart packages Keycloak with sensible defaults, a bundled PostgreSQL database, and extensive configuration options. If your team already uses Helm for other services, this approach has the lowest adoption friction.

Installation

# Add the Bitnami repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

# Install with default settings (development)
helm install keycloak bitnami/keycloak 
  --namespace keycloak 
  --create-namespace

Production values.yaml

# values.yaml
replicaCount: 3

auth:
  adminUser: admin
  existingSecret: keycloak-admin-credentials
  passwordSecretKey: admin-password

production: true
proxy: edge

# Resource limits
resources:
  requests:
    cpu: 500m
    memory: 1Gi
  limits:
    cpu: 2000m
    memory: 2Gi

# PostgreSQL configuration
postgresql:
  enabled: true
  auth:
    existingSecret: keycloak-db-credentials
    secretKeys:
      adminPasswordKey: postgres-password
      userPasswordKey: password
  primary:
    persistence:
      size: 20Gi
    resources:
      requests:
        cpu: 250m
        memory: 512Mi

# Or use an external database
# postgresql:
#   enabled: false
# externalDatabase:
#   host: my-rds-instance.abc123.us-east-1.rds.amazonaws.com
#   port: 5432
#   user: keycloak
#   database: keycloak
#   existingSecret: keycloak-external-db
#   existingSecretPasswordKey: password

# Ingress
ingress:
  enabled: true
  ingressClassName: nginx
  hostname: auth.example.com
  tls: true
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/proxy-buffer-size: "128k"
    nginx.ingress.kubernetes.io/proxy-buffers-number: "4"

# Health checks
livenessProbe:
  enabled: true
  initialDelaySeconds: 60
  periodSeconds: 10
readinessProbe:
  enabled: true
  initialDelaySeconds: 30
  periodSeconds: 10

# Keycloak configuration
extraEnvVars:
  - name: KC_FEATURES
    value: "token-exchange,admin-fine-grained-authz"
  - name: KC_METRICS_ENABLED
    value: "true"
  - name: KC_HEALTH_ENABLED
    value: "true"
  - name: KC_CACHE
    value: "ispn"
  - name: KC_CACHE_STACK
    value: "kubernetes"
  - name: JAVA_OPTS_APPEND
    value: >-
      -Djgroups.dns.query=keycloak-headless.keycloak.svc.cluster.local

# Pod disruption budget
pdb:
  create: true
  minAvailable: 2

# Autoscaling
autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 10
  targetCPU: 70
  targetMemory: 80

# Service monitor for Prometheus
metrics:
  enabled: true
  serviceMonitor:
    enabled: true
    namespace: monitoring

Deploy

helm install keycloak bitnami/keycloak 
  --namespace keycloak 
  --create-namespace 
  -f values.yaml

Upgrades

# Update chart version
helm repo update

# Upgrade with new values or chart version
helm upgrade keycloak bitnami/keycloak 
  --namespace keycloak 
  -f values.yaml

# Check rollout status
kubectl rollout status statefulset/keycloak -n keycloak

For detailed Keycloak upgrade strategies, see the best strategy to upgrade your Keycloak cluster.

Approach 2: Keycloak Operator

The Keycloak Operator is a Kubernetes-native way to manage Keycloak. It uses Custom Resource Definitions (CRDs) to declaratively define Keycloak instances and realm configurations. The operator watches for changes to these CRs and reconciles the cluster state automatically.

Install the Operator

# Install the operator (check for the latest version)
kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/refs/heads/main/kubernetes/kubernetes.yml 
  -n keycloak

Or with OLM (Operator Lifecycle Manager):

# If OLM is installed
kubectl create -f https://operatorhub.io/install/keycloak-operator.yaml

Deploy Keycloak with a CR

# keycloak.yaml
apiVersion: k8s.keycloak.org/v2alpha1
kind: Keycloak
metadata:
  name: keycloak
  namespace: keycloak
spec:
  instances: 3
  db:
    vendor: postgres
    host: postgres-service
    usernameSecret:
      name: keycloak-db-credentials
      key: username
    passwordSecret:
      name: keycloak-db-credentials
      key: password
  hostname:
    hostname: auth.example.com
  http:
    tlsSecret: keycloak-tls-secret
  proxy:
    headers: xforwarded
  features:
    enabled:
      - token-exchange
      - admin-fine-grained-authz
  transaction:
    xaEnabled: false
  resources:
    requests:
      cpu: 500m
      memory: 1Gi
    limits:
      cpu: 2000m
      memory: 2Gi
  additionalOptions:
    - name: metrics-enabled
      value: "true"
    - name: health-enabled
      value: "true"
  unsupported:
    podTemplate:
      spec:
        containers:
          - env:
              - name: JAVA_OPTS_APPEND
                value: >-
                  -Djgroups.dns.query=keycloak-discovery.keycloak.svc.cluster.local
kubectl apply -f keycloak.yaml -n keycloak

Realm Configuration with CRDs

The operator’s strongest feature is declarative realm management via KeycloakRealmImport:

# realm-import.yaml
apiVersion: k8s.keycloak.org/v2alpha1
kind: KeycloakRealmImport
metadata:
  name: myrealm-import
  namespace: keycloak
spec:
  keycloakCRName: keycloak
  realm:
    realm: myrealm
    enabled: true
    sslRequired: external
    registrationAllowed: false
    loginWithEmailAllowed: true
    duplicateEmailsAllowed: false
    resetPasswordAllowed: true
    bruteForceProtected: true
    permanentLockout: false
    maxFailureWaitSeconds: 900
    minimumQuickLoginWaitSeconds: 60
    waitIncrementSeconds: 60
    maxDeltaTimeSeconds: 43200
    failureFactor: 5
    accessTokenLifespan: 300
    ssoSessionIdleTimeout: 1800
    ssoSessionMaxLifespan: 36000
    clients:
      - clientId: my-web-app
        name: My Web Application
        enabled: true
        publicClient: false
        clientAuthenticatorType: client-secret
        secret: "$(env:MY_APP_SECRET:-changeme)"
        redirectUris:
          - "https://app.example.com/*"
        webOrigins:
          - "https://app.example.com"
        protocol: openid-connect
        defaultClientScopes:
          - openid
          - profile
          - email
      - clientId: my-mobile-app
        name: My Mobile Application
        enabled: true
        publicClient: true
        redirectUris:
          - "myapp://auth/callback"
        protocol: openid-connect
    roles:
      realm:
        - name: app-admin
          description: Application administrator
        - name: app-user
          description: Regular application user
      client:
        my-web-app:
          - name: manage-users
          - name: view-reports
    identityProviders:
      - alias: google
        providerId: google
        enabled: true
        config:
          clientId: "google-client-id"
          clientSecret: "google-client-secret"
          defaultScope: "openid profile email"
kubectl apply -f realm-import.yaml -n keycloak

The operator monitors this CR and applies the configuration to the running Keycloak instance. Changes to the CR trigger re-import. This is the CRD-based equivalent of what keycloak-config-cli does outside Kubernetes (see our realm export and import guide).

Certificate Management

The operator integrates with cert-manager for automatic TLS certificate provisioning:

# certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: keycloak-tls
  namespace: keycloak
spec:
  secretName: keycloak-tls-secret
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - auth.example.com

Reference the secret in your Keycloak CR under spec.http.tlsSecret.

Detailed Comparison

Configuration Management

Helm: Configuration lives in values.yaml. Changes require helm upgrade. You can use Helmfile or ArgoCD to manage values across environments. Realm configuration is not managed by Helm — you need external tools like keycloak-config-cli or the Admin API.

Operator: Configuration is split across Kubernetes CRs. The Keycloak CR manages the server, and KeycloakRealmImport CRs manage realm configuration. Changes are applied by updating the CRs. GitOps tools (ArgoCD, Flux) work naturally with CRs.

For a full ArgoCD-based deployment workflow, see how to deploy Keycloak in Kubernetes using ArgoCD.

High Availability

Helm: You set replicaCount and configure JGroups for cluster discovery. The Bitnami chart includes a headless service for DNS-based JGroups discovery. You manually configure PodDisruptionBudgets and anti-affinity rules.

Operator: You set instances in the Keycloak CR. The operator handles StatefulSet creation with proper discovery configuration. Pod disruption and rolling updates are managed by the operator’s reconciliation logic.

Both approaches require a shared database (PostgreSQL) and proper Infinispan/JGroups configuration for cache replication. For cluster tuning guidance, see top 7 Keycloak cluster configuration best practices.

Scaling

Helm: Use Kubernetes HPA with the autoscaling configuration in values.yaml. Scales based on CPU/memory metrics.

Operator: The operator can work with HPA, but you manage scaling by updating the instances field in the Keycloak CR. Custom metrics-based scaling requires additional configuration.

Monitoring

Both approaches expose the same Keycloak metrics at /metrics when metrics-enabled is set to true. The difference is in how you configure Prometheus scraping:

Helm: The Bitnami chart includes a ServiceMonitor resource when metrics.serviceMonitor.enabled is true.

Operator: You need to create the ServiceMonitor separately:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: keycloak-metrics
  namespace: keycloak
spec:
  selector:
    matchLabels:
      app: keycloak
  endpoints:
    - port: http
      path: /metrics
      interval: 30s

For production monitoring, Skycloak’s Insights dashboard provides pre-built metrics and alerting without the need to set up Prometheus yourself.

Upgrades

Helm: Run helm upgrade with a new chart version. The chart handles rolling updates through StatefulSet update strategies. You control the pace.

Operator: Update the operator version, then update the Keycloak CR’s image reference. The operator handles the rolling update, ensuring each pod is ready before proceeding.

Both methods require database migration awareness — Keycloak runs schema migrations on startup, and not all versions are backward-compatible. Always check the Keycloak release notes before upgrading.

Pros and Cons Summary

Helm Chart

Pros:

  • Familiar to anyone who uses Helm
  • Extensive configuration options in values.yaml
  • Bundled PostgreSQL for simple deployments
  • Large community with well-documented patterns
  • Easy to integrate with existing Helm-based CI/CD

Cons:

  • Realm configuration managed separately
  • No built-in reconciliation (changes require manual helm upgrade)
  • Bitnami chart may lag behind official Keycloak releases
  • Less “Kubernetes-native” than CRD-based management

Keycloak Operator

Pros:

  • Kubernetes-native CRD-based management
  • Declarative realm configuration with KeycloakRealmImport
  • Automatic reconciliation — the operator watches for drift
  • Official project from the Keycloak team
  • Natural fit for GitOps workflows (ArgoCD, Flux)

Cons:

  • Operator itself needs management and updates
  • The v2 operator is still maturing — some features are alpha
  • Database is not bundled — you manage it separately
  • Higher learning curve if team is unfamiliar with operators
  • Fewer community examples compared to Helm

Decision Framework

Choose Helm When

  • Your team already uses Helm extensively
  • You want a quick, opinionated setup with bundled database
  • You manage realm configuration through external tools (Terraform, keycloak-config-cli, Admin API)
  • You need fine-grained control over every deployment parameter
  • Your CI/CD pipeline is Helm-centric

Choose the Operator When

  • You want Kubernetes-native, CRD-driven configuration
  • You need declarative realm management alongside server management
  • You use GitOps (ArgoCD, Flux) as your deployment model
  • You want automatic reconciliation and drift detection
  • Your team is comfortable with Kubernetes operators and CRD patterns

Choose Neither When

  • You want to avoid the operational complexity of running Keycloak on Kubernetes entirely. Managed Keycloak hosting eliminates the need to manage infrastructure, regardless of the deployment method.

Production Checklist

Regardless of which approach you choose, ensure these items are addressed:

  • [ ] External database with replication and automated backups
  • [ ] TLS certificates with automatic renewal (cert-manager)
  • [ ] Resource requests and limits configured
  • [ ] PodDisruptionBudget set (minimum 2 available)
  • [ ] Health checks configured (liveness and readiness probes)
  • [ ] Metrics collection enabled (Prometheus + Grafana or similar)
  • [ ] Audit logging enabled for security events
  • [ ] Admin console access restricted (network policy or ingress rules). See path-based IP restriction for Keycloak admin console.
  • [ ] Backup and restore procedures tested
  • [ ] Upgrade runbook documented

For comprehensive cluster hardening, see securing your Keycloak master realm and 8 default configurations to adjust.

Further Reading

Wrapping Up

Both the Helm chart and the Keycloak Operator are valid production deployment methods. Helm is more accessible and battle-tested. The operator is more Kubernetes-native and offers declarative realm management. The best choice depends on your team’s existing tooling, GitOps maturity, and comfort with CRD-based workflows.

If managing Kubernetes infrastructure for your identity platform is not where you want to invest engineering time, Skycloak runs Keycloak for you with built-in high availability, security features, and guaranteed SLA. Visit our pricing page to compare plans, or explore our documentation for integration details.

Guilliano Molaire
Written by Guilliano Molaire Founder

Guilliano is the founder of Skycloak and a cloud infrastructure specialist with deep expertise in product development and scaling SaaS products. He discovered Keycloak while consulting on enterprise IAM and built Skycloak to make managed Keycloak accessible to teams of every size.

Ready to simplify your authentication?

Deploy production-ready Keycloak in minutes. Unlimited users, flat pricing, no SSO tax.

© 2026 Skycloak. All Rights Reserved. Design by Yasser Soliman