From a97399c2bfe5a41db84ac212c01bc4ae45034dd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Tailland?= Date: Fri, 28 Aug 2026 09:37:35 +0200 Subject: [PATCH] Import the realm on every start, so the file is the source of truth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit configmap-realm.yaml was a first-install seed: `start --import-realm` skips a realm that already exists and logs "Import skipped", so editing the file changed nothing on a restart and the truth drifted into the database. It already had — four scope grants existed only in the running realm. There is no option to make the startup import overwrite. The --override flag exists only on the standalone `import` command, and the Keycloak documentation is explicit that re-creating realms means running that command before starting the server. So the import moves into an init container that runs `import --dir ... --override true`, and --import-realm comes off the main container's args. The init container empties JAVA_OPTS_APPEND for itself. It inherits the main container's env, which loads the OpenTelemetry agent from a volume the other init container downloads into and this one does not mount; inherited verbatim, the JVM fails before the import runs. What this costs, deliberately: the realm is replaced at every start, so anything created at runtime and not written back to this file is lost, and active sessions are dropped. Checked before enabling it — the five users in the realm are the five declared here, and the only live sessions were two LibreChat ones. Nothing is to be created through the admin console expecting it to last. Verified after the switch: the import logs "Strategy: OVERWRITE_EXISTING", the realm still has its 21 clients with no optionalClientScopes divergence, and the full identity chain still returns 200 — pm-user token, exchanged by the Catalogue agent, accepted by catalog-mcp. Co-Authored-By: Claude Opus 5 --- k8s/keycloak/deployment.yaml | 115 ++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/k8s/keycloak/deployment.yaml b/k8s/keycloak/deployment.yaml index 9bf4511..50b176f 100644 --- a/k8s/keycloak/deployment.yaml +++ b/k8s/keycloak/deployment.yaml @@ -28,6 +28,120 @@ spec: prometheus.io/path: "/metrics" spec: initContainers: + # Re-imports the realm from configmap-realm.yaml on every start, so that + # file is the source of truth rather than a first-install seed. + # + # It has to be a separate command: `start --import-realm` skips a realm + # that already exists and there is no option to change that — the + # --override flag exists only on the standalone `import` command. With + # the default behaviour, editing the ConfigMap changed nothing on a + # restart and the truth drifted into the database. It already had: four + # scope grants existed only in the running realm. + # + # What this costs, deliberately: the realm is replaced at every start, + # so anything created at runtime and not written back to the file is + # lost, and active sessions are dropped. Users survive only because they + # are declared in the file. Create nothing through the admin console + # expecting it to last. + - name: import-realm + image: quay.io/keycloak/keycloak:26.6.0 + args: + - "import" + - "--dir" + - "/opt/keycloak/data/import" + - "--override" + - "true" + env: + - name: KC_DB + value: "postgres" + - name: KC_DB_URL_HOST + value: "keycloak-postgresql" + - name: KC_DB_URL_PORT + value: "5432" + - name: KC_DB_URL_DATABASE + valueFrom: + secretKeyRef: + name: keycloak-secrets + key: POSTGRES_DB + - name: KC_DB_USERNAME + valueFrom: + secretKeyRef: + name: keycloak-secrets + key: KC_DB_USERNAME + - name: KC_DB_PASSWORD + valueFrom: + secretKeyRef: + name: keycloak-secrets + key: KC_DB_PASSWORD + - name: KC_BOOTSTRAP_ADMIN_USERNAME + valueFrom: + secretKeyRef: + name: keycloak-secrets + key: KC_BOOTSTRAP_ADMIN_USERNAME + - name: KC_BOOTSTRAP_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: keycloak-secrets + key: KC_BOOTSTRAP_ADMIN_PASSWORD + - name: KEYCLOAK_ADMIN + valueFrom: + secretKeyRef: + name: keycloak-secrets + key: KEYCLOAK_ADMIN + - name: KEYCLOAK_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: keycloak-secrets + key: KEYCLOAK_ADMIN_PASSWORD + - name: KC_HTTP_ENABLED + value: "true" + - name: KC_PROXY_HEADERS + value: "xforwarded" + - name: KC_HOSTNAME + value: "https://keycloak.sttlab.local" + - name: KC_HOSTNAME_STRICT + value: "false" + - name: KC_HOSTNAME_BACKCHANNEL_DYNAMIC + value: "false" + - name: KC_FEATURES + value: "token-exchange,admin-fine-grained-authz,log-mdc" + # Emptied for this container only: it inherits the main container's env, + # which loads the OpenTelemetry agent from a volume the other init + # container downloads into and this one does not mount. Inheriting it + # verbatim fails the JVM before the import runs. + - name: JAVA_OPTS_APPEND + value: "" + - name: OTEL_SERVICE_NAME + value: "keycloak" + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: "http://tempo.observability.svc.cluster.local:4318" + - name: OTEL_EXPORTER_OTLP_PROTOCOL + value: "http/protobuf" + - name: OTEL_TRACES_EXPORTER + value: "otlp" + - name: OTEL_METRICS_EXPORTER + value: "none" + - name: OTEL_LOGS_EXPORTER + value: "none" + - name: KC_HTTP_ACCESS_LOG_ENABLED + value: "true" + - name: KC_LOG_LEVEL + value: "INFO,org.keycloak.events:DEBUG" + - name: KC_LOG_CONSOLE_OUTPUT + value: "json" + - name: KC_LOG_MDC_ENABLED + value: "true" + - name: KC_LOG_SERVICE_NAME + value: "keycloak" + - name: KC_LOG_SERVICE_ENVIRONMENT + value: "local" + - name: KC_HEALTH_ENABLED + value: "true" + - name: KC_METRICS_ENABLED + value: "true" + volumeMounts: + - name: realm-import + mountPath: /opt/keycloak/data/import - name: download-otel-agent image: curlimages/curl:8.10.0 command: @@ -42,7 +156,6 @@ spec: image: quay.io/keycloak/keycloak:26.6.0 args: - "start" - - "--import-realm" ports: - name: http containerPort: 8080