kadet-hub logokadet-hub

Keycloak on Kubernetes without Bitnami, with Kapitan

Keycloak with the Keycloak Operator, in place of Bitnami's keycloak chart: one instance on the upstream image, its database on a CloudNativePG cluster and its TLS certificate from cert-manager. The initial admin is in the Secret sso-initial-admin. The Issuer is self-signed so the recipe runs anywhere; point the Certificate at your own issuer and set the hostname. Needs cert-manager, CloudNativePG and the Keycloak Operator, which watches its own namespace, here keycloak.

Inventory class

inventory/classes/recipes/keycloak.yml, as on the main branch of kadet-hub/examples. A target includes it next to the pins below.

# Keycloak with the Keycloak Operator, in place of Bitnami's keycloak chart:
# one instance on the upstream image, its database on a CloudNativePG cluster
# and its TLS certificate from cert-manager. The initial admin is in the
# Secret sso-initial-admin. The Issuer is self-signed so the recipe runs
# anywhere; point the Certificate at your own issuer and set the hostname.
# Needs cert-manager, CloudNativePG and the Keycloak Operator, which watches
# its own namespace, here keycloak.
classes:
  - recipes.lib.kubernetes
  - recipes.lib.cloudnative-pg
  - recipes.lib.certmanager
  - recipes.lib.keycloak

parameters:
  generators:
    kubernetes:
      namespaces:
        keycloak: {}
    cloudnative-pg:
      clusters:
        keycloak-db:
          namespace: keycloak
          instances: 2
          image_name: ghcr.io/cloudnative-pg/postgresql:18.6-system-trixie
          storage: 10Gi
          bootstrap:
            initdb:
              database: keycloak
              owner: keycloak
    certmanager:
      issuers:
        keycloak-selfsigned:
          namespace: keycloak
          spec:
            selfSigned: {}
      certs:
        sso-tls:
          namespace: keycloak
          spec:
            secretName: sso-tls
            commonName: sso.example.com  # Keycloak rejects a certificate with an empty issuer DN
            issuerRef:
              name: keycloak-selfsigned
              kind: Issuer
            dnsNames:
              - sso.example.com
              - sso-service.keycloak.svc
    keycloak:
      instances:
        sso:
          namespace: keycloak
          instances: 1
          image: quay.io/keycloak/keycloak:26.8.1
          spec:
            startOptimized: false  # the operator assumes a prebuilt image when image is set
          db:
            vendor: postgres
            host: keycloak-db-rw
            database: keycloak
            credentials_secret: keycloak-db-app
          hostname:
            hostname: https://sso.example.com
          http:
            tls_secret: sso-tls
          ingress:
            enabled: false
          resources:
            requests: {cpu: 500m, memory: 1Gi}
            limits: {memory: 2Gi}

Compile classes

The class includes one per generator; each compiles its generator once, so a target can combine several recipes.

inventory/classes/recipes/lib/kubernetes.yml

# Compiles the kubernetes generator; recipes include this class, so a target that
# combines several recipes compiles it once.
parameters:
  kapitan:
    compile:
      - {input_type: kadet, output_type: yml, input_paths: [lib/kubernetes], output_path: kubernetes/}

inventory/classes/recipes/lib/cloudnative-pg.yml

# Compiles the cloudnative-pg generator; recipes include this class, so a target that
# combines several recipes compiles it once.
parameters:
  kapitan:
    compile:
      - {input_type: kadet, output_type: yml, input_paths: [lib/cloudnative-pg], output_path: cloudnative-pg/}

inventory/classes/recipes/lib/certmanager.yml

# Compiles the certmanager generator; recipes include this class, so a target
# that combines several recipes compiles it once.
parameters:
  kapitan:
    compile:
      - {input_type: kadet, output_type: yml, input_paths: [lib/certmanager], output_path: certmanager/}

inventory/classes/recipes/lib/keycloak.yml

# Compiles the keycloak generator; recipes include this class, so a target that
# combines several recipes compiles it once.
parameters:
  kapitan:
    compile:
      - {input_type: kadet, output_type: yml, input_paths: [lib/keycloak], output_path: keycloak/}

Generator pins

The entries of inventory/classes/generators.yml for these generators, pinned by version and digest.

parameters:
  kapitan:
    dependencies:
    - output_path: lib/cloudnative-pg
      source: ghcr.io/kadet-hub/cloudnative-pg:1.0.1@sha256:281417a421b0053070fbdc552f9032a3f8d564c0ca2047ec500842a0d060a8f7
      type: oci
    - output_path: lib/keycloak
      source: ghcr.io/kadet-hub/keycloak:1.0.3@sha256:0902cc8a796e2a1d1708c7f9975e41e8422d0d1edff7f873faa5ecaeced13dfd
      type: oci
    - output_path: lib/certmanager
      source: ghcr.io/kadet-hub/certmanager:1.0.2@sha256:ba7632e9e06a593fbe20e9fa6774139203d7df65992adff9db94779606888ad9
      type: oci
    - output_path: lib/kubernetes
      source: ghcr.io/kadet-hub/kubernetes:1.2.0@sha256:52017626e77dd5bf7293ce465ff0c3dc7a61dae1ae8353a56a53ffebe1bfb67c
      type: oci

How it is checked

The examples CI verifies the signed index and the attestation of every pinned generator, compiles the target, validates the output against the generators' schemas, deploys it to a new kind cluster with the operators above and fails unless the Keycloak is Ready and /realms/master answers 200 over TLS. The Chainsaw test holds the assertions.

All recipes · Bitnami chart alternatives