Deploying Keycloak on GKE with a custom Helm Chart

February 27, 2025

Deploying Keycloak on GKE with a custom Helm Chart

In this guide, you'll learn how to deploy Keycloak on Google Kubernetes Engine (GKE) using:

  • A Chart.yaml that specifies dependencies (Bitnami Keycloak),
  • values.yaml for configuration details,
  • ingress.yaml to define how traffic enters your cluster,
  • managed-cert.yaml for an SSL certificate managed by GKE,
  • keycloak-argo.yaml for automated GitOps via Argo CD (optional).

1. Prerequisites

  1. GKE cluster

    • You'll need a functional GKE cluster. Ensure kubectl is pointing to it:
    kubectl config get-contexts
    
  2. Helm (v3+)

    • Verify Helm is installed:
    helm version
    
  3. Bastion host (if needed)

    • If your cluster is private, confirm you can SSH into a bastion host with internal cluster access.
  4. Argo CD (optional)

    • If you wish to automate deployments, have Argo CD installed in the cluster.
  5. Reserved static IP (optional)

    • Consider reserving a global static IP in GCP for a consistent external IP.

2. Chart structure

Below is a common structure for your custom Keycloak chart. Put these files in a directory (e.g., keycloak/) in your Git repository:

keycloak/
├── Chart.yaml
├── values.yaml
├── templates/
│   ├── ingress.yaml
│   ├── managed-cert.yaml
│   └── _other_templates_.yaml
├── keycloak-argo.yaml
└── README.md (optional)

You can place keycloak-argo.yaml either in the root of your repo or alongside your chart files.


3. Chart.yaml

Purpose: Defines your Helm chart's name, version, description, and dependencies.

View Chart.yaml configuration
apiVersion: v2
name: keycloak
description: A wrapper chart for Bitnami Keycloak
version: 0.1.0
appVersion: "24.4.10"
dependencies:
  - name: keycloak
    version: "24.4.10"
    repository: "https://charts.bitnami.com/bitnami"

Key points:

  • apiVersion: v2 is required for Helm 3.
  • dependencies: References the Bitnami Keycloak chart and its version.
  • name, version, and appVersion can be customized to fit your needs.

When you run helm dependency update in this chart's directory, Helm will fetch the Bitnami Keycloak chart from the specified repository.


4. values.yaml

Purpose: Holds the main configuration for Keycloak, its database, and resources. These values override or supplement the Bitnami chart's defaults.

View values.yaml configuration
keycloak:
  # Enable production mode
  production: true

  # TLS Configuration
  tls:
    enabled: true
    autoGenerated: true

  # Configure proxy settings for edge termination
  proxy: edge
  httpRelativePath: /

  # Pod configuration
  replicaCount: 1
  resources:
    requests:
      memory: 256Mi
      cpu: 250m
    limits:
      memory: 512Mi
      cpu: 500m
  persistence:
    enabled: true
    storageClass: "standard"
    accessModes:
      - ReadWriteOnce
    size: 8Gi

  # Configure authentication
  auth:
    adminUser: admin
    adminPassword: "mySecureAdminPassword"

  # Extra environment variables
  extraEnvVars:
    - name: KC_HOSTNAME
      value: "keycloak.mydomain.com"
    - name: KC_HOSTNAME_STRICT
      value: "true"
    - name: KC_HOSTNAME_STRICT_HTTPS
      value: "true"
    - name: KC_PROXY
      value: "edge"
    - name: KEYCLOAK_LOG_LEVEL
      value: DEBUG

  # Ingress configuration (used by our custom template)
  ingress:
    enabled: false # Disabled because we use a custom Ingress template
    hostname: keycloak.mydomain.com

  postgresql:
    enabled: true
    auth:
      postgresPassword: "postgrespassword"
      username: keycloak
      password: "keycloakpassword"
      database: keycloak_db
    persistence:
      enabled: true
      storageClass: "standard"
      accessModes:
        - ReadWriteOnce
      size: 8Gi

Key points:

  • production: true: Enables stricter security settings.
  • proxy: edge: Configures Keycloak for edge-terminated SSL.
  • auth.adminPassword: Must be changed before production use.
  • postgresql.enabled: true: Deploys an internal PostgreSQL instance with persistent storage.
  • ingress.enabled: false: Because we define an Ingress resource in a separate template file (ingress.yaml), we disable the default Bitnami Keycloak ingress.

5. ingress.yaml

In a typical Helm chart, you'd place this file under templates/ingress.yaml.

View ingress.yaml configuration
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ .Release.Name }}-ingress
  namespace: {{ .Release.Namespace }}
  annotations:
    kubernetes.io/ingress.class: "gce"
    kubernetes.io/ingress.global-static-ip-name: "keycloak-global-ip"
    networking.gke.io/managed-certificates: {{ .Release.Name }}-cert
    # Temporarily allow HTTP while certificate is provisioning
    kubernetes.io/ingress.allow-http: "true"
spec:
  ingressClassName: gce
  rules:
    - host: {{ .Values.keycloak.ingress.hostname }}
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: {{ .Release.Name }}
                port:
                  number: 80

Key points:

  • kubernetes.io/ingress.class: "gce": Uses Google Cloud Load Balancing.
  • kubernetes.io/ingress.global-static-ip-name: Assign your static IP name here (if you reserved one).
  • allow-http: "true": Lets HTTP traffic pass while the SSL certificate is pending. You can remove or change it later.

6. managed-cert.yaml

Also stored in templates/ (e.g., templates/managed-cert.yaml). This resource requests and manages an SSL certificate for your domain.

View managed-cert.yaml configuration
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
  name: {{ .Release.Name }}-cert
  namespace: {{ .Release.Namespace }}
spec:
  domains:
    - {{ .Values.keycloak.ingress.hostname }}

Key points:

  • domains: Should match the domain in the Ingress host.
  • It may take up to 30 minutes for GCP to issue and activate the certificate.

7. keycloak-argo.yaml (Optional)

If you want a GitOps approach with Argo CD, you can define an Argo CD Application pointing to this Helm chart. You can place it at your repo's root or in the keycloak/ folder.

View keycloak-argo.yaml configuration
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: keycloak
  namespace: argocd
spec:
  project: default
  source:
    repoURL: "[email protected]:my-org/keycloak.git"
    path: "."
    targetRevision: HEAD
  destination:
    server: "https://kubernetes.default.svc"
    namespace: keycloak
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Key points:

  • repoURL: Set to your repository.
  • path: If your chart is in a subdirectory (e.g., keycloak/), adjust accordingly.
  • prune and selfHeal: Ensures your cluster stays in sync with Git at all times.

8. Deployment steps

8.1 (optional) Prepare dependencies

If you have defined dependencies in Chart.yaml (like Bitnami Keycloak), run:

helm dependency update path/to/keycloak/

This downloads the Bitnami Keycloak chart locally so Helm can merge it with your wrapper.

8.2 Using Helm CLI

If you're deploying manually:

helm upgrade --install keycloak path/to/keycloak/ \
  --namespace keycloak \
  --create-namespace

Helm will render Chart.yaml, values.yaml, and everything under templates/.

8.3 Using Argo CD

  1. Push changes to Git
    Commit and push your chart files (Chart.yaml, values.yaml, templates/, etc.) along with keycloak-argo.yaml.
  2. Argo CD sync
    • Forward Argo CD if needed:
      kubectl port-forward svc/argocd-server -n argocd 8081:443
      
    • Go to https://localhost:8081 and look for the keycloak application. Argo CD will either auto-sync or you can press "Sync" to deploy.

9. Post-deployment checks

  1. Pods:

    kubectl get pods -n keycloak
    
    • Look for Keycloak and PostgreSQL pods in a "Running" state.
  2. Ingress:

    kubectl get ingress -n keycloak
    
    • An external IP should appear once the LB is set up.
  3. Managed Certificate:

    kubectl describe managedcertificate -n keycloak
    
    • Wait for "Active" before HTTPS is fully functional.
  4. Browser test:

    • Visit https://keycloak.mydomain.com (or whichever domain you set).
    • You should see Keycloak's login screen.

10. Troubleshooting tips

  • Certificate provisioning
    • It can take up to 30 minutes. Ensure your DNS record points to the external IP.
  • Pod crashes or failures
    kubectl logs -n keycloak -l app.kubernetes.io/name=keycloak
    
    • Check for issues like incorrect database credentials or insufficient memory.
  • Argo CD errors
    • Inspect the "Events" or "Application" details in the Argo CD UI.
    • Make sure your repoURL, path, and Chart.yaml references are correct.
  • Ingress not resolving
    • Confirm your domain DNS points to the Ingress external IP.
    • If using a static IP, ensure the name matches kubernetes.io/ingress.global-static-ip-name.

11. Security best practices

  1. Use secure passwords
    • Update keycloak.auth.adminPassword and the database credentials before production.
  2. Production mode
    • Leave production: true to enforce stricter security settings.
  3. TLS
    • Use GKE-managed certificates externally and auto-generated self-signed certs internally to ensure end-to-end encryption.
  4. Argo CD prune & self-heal
    • Test changes in a staging environment first; these features can remove resources or revert manual changes unexpectedly if you're not prepared.