# ServiceDefinition: keycloak # Source of truth: spec docs/13-service-catalog.md row 0; docs/15-identity-and-access.md; ADR-0014. # public IdP | egress internet | PostgreSQL on Longhorn (irreplaceable, DR-critical) | auth native. # Keycloak IS the IdP (root of trust): its own admin login is local + MFA, so it does not federate. # Validates against schemas/servicedefinition.schema.json. name: keycloak kind: container # DOCUMENTATION-ONLY IMAGE. The running server is NOT chosen here: the Keycloak operator # (platform/keycloak/kustomization.yaml, three pinned release URLs) picks it via its # RELATED_IMAGE_KEYCLOAK, and the Keycloak CR has no spec.image. This SD's render, # apps/wave-1/keycloak.yaml, is not listed in apps/wave-1/kustomization.yaml and deploys nothing # (ci/render-golden.sh RENDER_GOLDEN_NOT_ONBOARDED). The tag records the operator's version for # readers; renovate.json5 disables Renovate for this image so it cannot raise PRs against a file # nothing deploys. The Keycloak version moves with the operator — update this tag by hand then. source: image: quay.io/keycloak/keycloak:26.0.6 exposure: public # login/redirect flows for internet-exposed services; hardened (docs/15; R-DNS/R-NET) egress: internet # normal WAN egress (docs/03; R-NET-05) storage: - name: db size: 10Gi class: longhorn # PostgreSQL on 3-replica Longhorn; irreplaceable (docs/15, docs/04; R-STATE-02) dependencies: - to: keycloak-db # explicit allow: Keycloak -> its PostgreSQL (docs/13 graph; R-NET-02) port: 5432 protocol: tcp ingressRoutes: - host: keycloak.${BASE_DOMAIN} port: 8080 # HTTP listener; TLS terminates at the Gateway (docs/08, docs/09) auth: mode: native # it IS the IdP; own admin login is local + MFA (docs/15; ADR-0014). Documented exception. update: channel: stable # R-UPD-01 range: ">=24.0.0" cadence: "0 4 * * *" autoRollbackOnFailedHealth: true # R-UPD-03 observability: metrics: true slis: - up # IdP down/unhealthy is a critical alert (docs/10; R-OBS-01) # WHAT "up" IS for Keycloak. The Keycloak OPERATOR runs the server as a StatefulSet named # `keycloak` (pods keycloak-0/1), not as the Deployment this ServiceDefinition would otherwise # imply — the operator owns the workload (platform/keycloak/keycloak-instance.yaml) and this SD # is the catalog's declaration of intent for it. Without this override the rendered SLI would # read a Deployment series that does not exist and its `absent()` half would fire FOREVER. # Verified on prod 2026-09-13: kube_statefulset_status_replicas_ready{namespace="keycloak", # statefulset="keycloak"} = 2, and the only Deployment in that namespace is keycloak-operator. livenessWorkload: kind: StatefulSet name: keycloak backup: policy: daily-offcluster # DR-critical: rebuilds from DB + git realm config (docs/15, docs/04; R-STATE-03)