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.
- Replaces the Bitnami charts:
keycloak - Generators: kubernetes, cloudnative-pg, certmanager, keycloak
- Operators: Keycloak Operator, CloudNativePG, cert-manager
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.