Skip to content

Example: Keycloak in-cluster

The generic recipe is in Onboard an app. This page is the Keycloak fill-in on this company’s first Project Loom node. Not a special snowflake. The next server copies workloads/keycloak/{base,overlays} and changes hosts.

The operator lives in namespace keycloak. Each instance is an ApplicationSet app: keycloak-staging and keycloak-prod.

flowchart TD
  Op[apps/keycloak-operator] --> Operator[Keycloak Operator]
  AS[apps/workloads.yaml] --> Stg[overlays/staging]
  AS --> Prod[overlays/prod]
  Stg --> CR1[Keycloak CR name keycloak]
  Stg --> PG1[keycloak-pg]
  Prod --> CR2[Keycloak CR name keycloak]
  Prod --> PG2[keycloak-pg]
  Operator -. manages .-> CR1
  Operator -. manages .-> CR2

apps/keycloak-operator.yaml syncs platform/keycloak-operator into keycloak, pinned to 26.6.3, ServerSideApply (CRDs are large).

workloads/keycloak/base/keycloak.yaml is HTTP-only behind Traefik. Overlay patches set hostname and Ingress host.

spec:
instances: 1
db:
vendor: postgres
host: keycloak-pg-rw
database: keycloak
http:
httpEnabled: true
ingress:
enabled: false

CNPG cluster keycloak-pg (secret keycloak-pg-app). Operator Service keycloak-service on :8080. TLS Secret keycloak-tls. UAT uat.auth.webbies.dev (overlay still staging), prod auth.webbies.dev.

keycloak-config-cli (Sync Job) applies workloads/keycloak/overlays/<env>/realms/*.yaml. Not KeycloakRealmImport. First-time service account: Provision §8. Lockout later: Restore.

App-specific clients (Penvoice’s public PKCE client, …) live in those realm files because that product is on this node. The next app adds its own realm file if it needs one — Talon doesn’t: it verifies its own credentials and issues its own session, so it has no client here. Using this Keycloak instance is a choice per app, not a platform requirement.

The API is a resource server: it validates JWTs against that env’s issuer. Issuer URL is an Infisical value, not Git. Same pattern for any OIDC app.