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
Operator
Section titled “Operator”apps/keycloak-operator.yaml syncs platform/keycloak-operator into keycloak, pinned to 26.6.3,
ServerSideApply (CRDs are large).
Instance + Postgres
Section titled “Instance + Postgres”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: falseCNPG 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.
Realms
Section titled “Realms”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.
Auth flow (Penvoice on this node)
Section titled “Auth flow (Penvoice on this node)”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.