Keycloak IAM — operator's reference skill
This skill covers running, configuring, deploying, and integrating with Keycloak, the open-source identity & access management server. It targets the modern Quarkus-based distribution (24.x → 26.7.x as of July 2026); for instances still in the field on 16.x–23.x — including the WildFly "-legacy" builds that ended at 19.0.3 — and for verifying realm migrations off them, route to legacy-and-migration.md. Information is current as of Keycloak 26.7.0 (released 2026-07-09); the body below is still written against the 26.6 feature set, so treat 26.7-only features as unresearched here and read the release notes directly.
The Red Hat build of Keycloak (RHBK) is downstream of upstream Keycloak with longer support windows and the same surface area; advice here applies to both unless explicitly noted.
Siblings in the k8s plugin. Keycloak is almost always deployed behind
something and packaged by something, and both are common failure sources rather
than Keycloak bugs: reverse-proxy headers, TLS termination and rate limiting in
front of it are traefik-hardening (a Keycloak that emits wrong issuer URLs
or loops on redirect is usually a proxy-header problem, not an IAM one);
authoring or fixing the chart that deploys it is helm; running it under
OpenShift's SCC and arbitrary-UID constraints is openshift-app.
How to use this skill
Keycloak is a large product. Don't try to load everything — route to one or two reference files based on what the user is asking, then go deep.
references/
├── server-config.md → CLI/env vars, kc.sh, kcadm.sh, hostname, db, cache,
│ TLS/proxy, logging, features, bootstrap-admin, KCRAW_
├── k8s-deployment.md → Operator install, Keycloak CR, KeycloakRealmImport,
│ raw manifests, HA topology, probes, autoscaling
├── security-hardening.md → Realm policies, brute force, FGAP v2, client policies
│ (FAPI/OAuth 2.1), DPoP, redirect URI safety, recent CVEs
├── integration.md → OIDC/SAML flows, IdP brokering, LDAP/AD federation,
│ themes, SPIs, admin clients (Java/JS/kcadm/Terraform)
├── observability.md → Metrics, OTLP tracing, structured logging, health
│ probes, troubleshooting recipes
├── upgrade-and-backup.md → Upgrade matrix, zero-downtime patches, realm
│ export/import, DB backup, disaster recovery
└── legacy-and-migration.md → Identifying/operating old versions (16.x–23.x,
WildFly "-legacy" builds), per-version breaking-
change ladder 16→current, and verifying a migrated
realm/clients against the pre-migration export
(normalize+diff recipe + expected-diffs allowlist)
Routing cheatsheet:
| User question |
Read first |
| "How do I set the hostname / proxy / DB?" |
server-config.md |
| "Why is my login redirecting in a loop?" |
server-config.md (hostname/proxy) |
| "How do I deploy Keycloak on Kubernetes?" / "Operator vs raw manifests" |
k8s-deployment.md |
"Show me a Keycloak CR" / "What goes in spec.*?" |
k8s-deployment.md + assets/examples/ |
| "How do I do zero-downtime upgrades?" |
k8s-deployment.md + upgrade-and-backup.md |
| "Harden my realm" / "FAPI / OAuth 2.1 / DPoP / FGAP" |
security-hardening.md |
| "Recent CVEs?" / "Is 26.x.y vulnerable?" |
security-hardening.md §CVE table |
| "Wire my SPA / mobile app / service to Keycloak" |
integration.md |
| "Configure SAML / OIDC IdP / LDAP / social login" |
integration.md |
| "Custom theme / authenticator / event listener / mapper" |
integration.md §SPIs / §Themes |
| "What metrics / Grafana dashboard / Prometheus / OTLP" |
observability.md |
| "Realm export gotchas" / "How do I back up?" / "Liquibase migration stuck" |
upgrade-and-backup.md |
"What version is this old instance?" / anything on WildFly / -legacy / /auth paths / 16.x–23.x |
legacy-and-migration.md §1–2 |
| "We're on 1x.x — what breaks if we upgrade?" |
legacy-and-migration.md §3 |
| "Did the migration keep all realm/client settings?" / compare realm before vs after |
legacy-and-migration.md §4 + assets/compare/ |
When in doubt about a CLI flag, the source of truth is https://www.keycloak.org/server/all-config (full option index). When in doubt about a CR field, the source of truth is the CRD YAML in keycloak-k8s-resources at the version tag (see §"Authoritative sources" below).
Version map (May 2026)
Latest stable: Keycloak 26.7.0 (released 2026-07-09). The 26.6 line continued to 26.6.4 (2026-06-26) and both 26.6.3 and 26.6.4 are security batches, so anything still on 26.6.0–26.6.2 is missing fixes. Do not quote specific CVE IDs from memory; pull the authoritative list with gh api repos/keycloak/keycloak/security-advisories and gh release view <version> --repo keycloak/keycloak --json body. The feed alone is not enough — its first_patched_version fields are empty, so read the candidate releases' notes to learn which version actually closes a CVE. See security-hardening.md §CVE table for how to surface the current set.
Notable changes through the 26.x line:
| Version |
Key changes (operator-relevant) |
| 26.7.x |
SCIM user provisioning (preview), multi-cluster HA without external caches (preview — supersedes the external-Infinispan topology this skill documents), OpenID Shared Signals Framework (experimental), Identity Brokering API V2 (V1 deprecated, still default-on), step-up auth for SAML clients, HAProxy + Traefik reverse-proxy blueprints. Not yet researched at reference depth — read the release notes |
| 26.6.x |
Workflows, JWT Authorization Grant, Federated client auth, Zero-downtime patch updates, KCRAW_ env prefix, automatic K8s truststore, graceful HTTP shutdown, configurable Service name/port in Operator, organization groups, sensitive-info redaction in HTTP access logs. 26.6.2, 26.6.3 and 26.6.4 are all security batches; 26.6.4 (2026-06-26) is the last 26.6 patch seen — see the advisory feed for the CVE sets they close |
| 26.5.x |
Token Exchange Standard (RFC 8693) GA, declarative-user-profile GA, FIPS via Bouncy Castle, ECC keys default for new realms, Java 25 added (server image still on JDK 21 for FIPS) |
| 26.4.x |
hostname-v2 GA + hostname-v1 removed, Quarkus 3.20 LTS, --proxy-headers (replaces `--proxy edge |
| 26.3.x |
Organizations GA, Account Console v3 GA, Admin UI on PatternFly React, FGAP v2 preview |
| 26.2.x |
OIDC client policies v2, persistent user sessions GA |
| 26.0.0 |
KEYCLOAK_ADMIN removed → KC_BOOTSTRAP_ADMIN_*, persistent user sessions on by default, hostname-v2 by default |
Always cross-check with the release-notes body via gh release view <tag> --repo keycloak/keycloak --json body --jq '.body'. Do not rely solely on this table — bump the date and verify.
Authoritative sources
When the user asks about something specific, prefer these sources over generic recall. Cited paths are relative to repo root — access via a local clone, or fetch via gh api repos/<owner>/<repo>/contents/<path>.
Upstream source (keycloak/keycloak)
- CLI option mappers:
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/*PropertyMappers.java
- Option definitions (descriptions, defaults):
quarkus/config-api/src/main/java/org/keycloak/config/*Options.java
- Operator Java code + CRD generators:
operator/src/main/java/org/keycloak/operator/
- Themes (login/email/account/admin):
themes/src/main/resources/theme/ + js/apps/{account-ui,admin-ui}/
- asciidoc guides operators rarely think to read:
docs/guides/server/*.adoc, docs/guides/operator/*.adoc, docs/guides/high-availability/*.adoc
Operator install manifests (keycloak/keycloak-k8s-resources)
- Each Keycloak version has a git tag (e.g.
26.7.0) with the manifests under kubernetes/. Through 26.6 that was three files; 26.7+ has more, and the operator crash-loops (Couldn't start informer for keycloaksamlclients...) if the extra CRDs aren't applied — vendor the whole directory listing, not a remembered file list:
keycloaks.k8s.keycloak.org-v1.yml — the Keycloak CRD
keycloakrealmimports.k8s.keycloak.org-v1.yml — the KeycloakRealmImport CRD
keycloakoidcclients…/keycloaksamlclients….yml (26.7+) — client CRDs, required by the operator even if unused
kubernetes.yml — Operator Deployment + RBAC + ServiceAccount (assumes install namespace keycloak; one hardcoded ClusterRoleBinding subject — use the docs' kustomize-overlay approach for other namespaces)
kustomization.yml, cluster-wide/ (26.7+)
- The repo's
main branch has only the README + LICENSE; the actual manifests live in tags. Use git checkout <tag> against a clone, or gh api repos/keycloak/keycloak-k8s-resources/contents/kubernetes?ref=<tag> to fetch raw. (Do NOT assume the repo is stale just because main looks empty.)
Online docs: https://www.keycloak.org/
/server/all-config — every CLI option, every default
/server/configuration — concept overview, build-time vs runtime
/operator/installation, /operator/basic-deployment, /operator/advanced-configuration, /operator/realm, /operator/keycloak-cr, /operator/rolling-updates
/high-availability/ — multi-cluster, external Infinispan, CloudNativePG recipe
/securing-apps/ — OIDC layers, SAML, DPoP, JWT-Authz-Grant, MCP authorization server (CIMD)
/docs/<version>/server_admin/ and /docs/<version>/server_development/ — admin and SPI guides
gh CLI (per the user's gh-cli-preferred rule):
- Release notes:
gh release view <tag> --repo keycloak/keycloak --json body --jq '.body'
- Issues:
gh issue view <N> --repo keycloak/keycloak
- Search:
gh search issues --repo keycloak/keycloak "<query>"
- Security advisories:
gh api repos/keycloak/keycloak/security-advisories
When the user references a specific behavior, check the source clone or CRD YAML before answering — option names, defaults, and JSON field names get renamed across releases (e.g. --proxy edge|reencrypt|passthrough → --proxy-headers xforwarded|forwarded in 26.4; KEYCLOAK_ADMIN → KC_BOOTSTRAP_ADMIN_* in 26.0; the hostname-v2 rewrite). Stale answers are worse than "let me check."
Production guardrails (the checklist that matters)
These are the things that bite operators most. Don't suggest a Keycloak deployment that violates them without a stated reason.
- Run
kc.sh build before start --optimized in a custom image, or set spec.startOptimized: true only after baking the build. Otherwise startup pays the auto-build cost on every pod start.
--hostname must be a real, externally-resolvable URL in production, with --hostname-strict=true (default). Do not run with hostname-strict=false outside of dev — it lets clients dictate the issuer.
--proxy-headers=xforwarded|forwarded is required when behind any reverse proxy that does TLS termination or rewrites the Host header. Pair with --proxy-trusted-addresses to a CIDR that covers the proxy. Without this, login redirects loop.
- Use
--db postgres (or another supported vendor). The default dev-file H2 is not for production and silently disables clustering. Postgres is the only DB that gets tested under load by upstream.
- Use a real container image registry / pin a tag (
quay.io/keycloak/keycloak:26.7.0), never latest. The nightly tag is for CI only.
- Probes go to the management port (default 9000):
/health/started, /health/live, /health/ready — not the main HTTP port. As of 26.6, probes return UP during DB migrations so Liquibase can finish without K8s killing the pod.
- HPA on Keycloak is a trap. Sessions live in clustered Infinispan caches; scaling out and back in churns the cache. Run a fixed number of replicas (≥3 for HA) with a
PodDisruptionBudget, not an HPA.
- Realm exports are NOT backups. They omit secrets, federated users, and event history. The Postgres database is the source of truth — back that up with WAL archiving (CloudNativePG, Crunchy, RDS automated backups).
- Bootstrap admin is temporary.
KC_BOOTSTRAP_ADMIN_USERNAME/PASSWORD exists only to create the first real admin via kcadm.sh, then should be removed. The bootstrap admin auto-expires after 120 minutes.
- Pin Keycloak ↔ Operator versions together. The operator at tag
26.7.0 is meant to manage Keycloak 26.7.0. Mixing major.minor versions across the operator/server boundary is unsupported and often breaks the CRD schema.
Quickstart: the smallest production-shaped Keycloak
When the user says "just stand one up so I can play," don't point them at kc.sh start-dev if they care about production fidelity — show them this instead. It's a Keycloak CR + Postgres + Ingress, no extra moving parts:
# 1. Install the operator (do this once per cluster)
# kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.7.0/kubernetes/keycloaks.k8s.keycloak.org-v1.yml
# kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.7.0/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml
# kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.7.0/kubernetes/kubernetes.yml
# 2. Create a TLS secret (cert-manager / hand-roll / etc.)
# 3. Create DB credentials secret (keys: username, password)
# 4. Deploy Keycloak
apiVersion: k8s.keycloak.org/v2alpha1
kind: Keycloak
metadata:
name: keycloak
namespace: iam
spec:
instances: 2
image: quay.io/keycloak/keycloak:26.7.0
startOptimized: false # set true when the image is pre-baked with `kc.sh build`
hostname:
hostname: https://auth.example.com
strict: true
proxy:
headers: xforwarded
http:
tlsSecret: keycloak-tls # remove and use ingress for edge termination
db:
vendor: postgres
host: postgres-rw.iam.svc
database: keycloak
usernameSecret: { name: keycloak-db, key: username }
passwordSecret: { name: keycloak-db, key: password }
bootstrapAdmin:
user:
secret: keycloak-bootstrap-admin # keys: username, password
update:
strategy: Auto # zero-downtime patch updates (26.6+)
features:
enabled: ["organizations", "admin-fine-grained-authz:v2"]
See assets/examples/ for fuller examples (with HA tuning, KeycloakRealmImport, raw-manifest deployment without the operator, network policies).
Common operator pitfalls
"What version am I running?" from inside a realm — deliberately absent from every UI except master's Server info page (version disclosure = hardening). Non-master admins with manage-realm get the version from GET /admin/serverinfo (26.5.4+): kcadm.sh get serverinfo | jq -r .systemInfo.version — note kcadm's --fields is useless here (it trims systemInfo but still dumps the ~0.5 MB remainder of the document); the view-system workaround was removed in 26.7. Infra-side: image tag, boot log line, or kcadm.sh help | head -1 in the pod.
"What's the admin URL for realm X?" — not discoverable in the UI. Pattern: /admin/{login-realm}/console/#/{managed-realm} — the first segment picks which realm authenticates you (its security-admin-console client), the fragment picks the realm being edited. Realm-local admins: /admin/<their-realm>/console/ (no realm dropdown, their realm only). Master admins: /admin/master/console/#/<realm>. End-user self-service: /realms/<realm>/account/. Prefix all with the server's relative path (/auth on legacy-layout installs).
hostname-strict=false "fixes" my login → No, it papers over a proxy/headers misconfig. Set --proxy-headers correctly instead.
"Why is my custom theme not appearing?" → The theme must be baked into a custom image and kc.sh build re-run; dropping a theme jar into a stock image at runtime no longer works. Themes are packaged into the optimized server jar.
"Operator pod logs are flooded with warnings" → On 26.6.0 exactly, that's #47872, fixed in 26.6.1. Upgrade to the latest 26.6.x.
"Realm import keeps failing on existing realm" → KeycloakRealmImport is create-or-replace; it won't merge. Use kcadm.sh for incremental edits, or accept the realm-as-config GitOps trade-off.
"My JS admin client is broken on 26.6.0" → 26.6.0 shipped a broken @keycloak/keycloak-admin-client package, fixed in 26.6.1. Use the latest 26.6.x.
"Liquibase changelock stuck" → A previous migration crashed mid-flight. DELETE FROM DATABASECHANGELOGLOCK WHERE ID=1 (after confirming no other instance is migrating). See observability.md §troubleshooting for the safe procedure.
"I'm getting KC_* substitution surprises with $ in passwords" → Use the new KCRAW_* prefix (26.6+) instead of KC_* for any value that contains $.
Style
- When citing options, give the exact flag and the env var:
--proxy-headers / `KC_PROXY_HEADERS` . Operators copy-paste; getting it half right costs them an hour.
- Prefer linking to the source file or release-notes URL the user can verify, over restating from memory. Releases between 26.0 and 26.6 renamed flags and removed others; "I'm pretty sure" is wrong about half the time.
- When the user is on a pre-26 version, surface the upgrade urgency briefly (a one-liner pointing at the upgrade guide). Don't lecture.
- The user is sophisticated — skip the "Keycloak is an open-source IAM solution from Red Hat" preamble. Get to the answer.
1---2name: keycloak-iam3description: Operate, configure, deploy, secure, and integrate with Keycloak (open-source IAM) — the modern Quarkus distribution (24.x–26.7.x), the Keycloak Operator with `Keycloak` and `KeycloakRealmImport` CRDs, realm/client/identity-provider configuration, plus legacy 16.x–23.x instances (incl. WildFly "-legacy" builds) and verifying realm/client config survived a version migration.4---56# Keycloak IAM — operator's reference skill78This skill covers running, configuring, deploying, and integrating with **Keycloak**, the open-source identity & access management server. It targets the modern **Quarkus-based** distribution (24.x → 26.7.x as of July 2026); for instances still in the field on 16.x–23.x — including the WildFly **"-legacy"** builds that ended at 19.0.3 — and for verifying realm migrations off them, route to `legacy-and-migration.md`. Information is current as of **Keycloak 26.7.0** (released 2026-07-09); the body below is still written against the 26.6 feature set, so treat 26.7-only features as unresearched here and read the release notes directly.910The Red Hat build of Keycloak (RHBK) is downstream of upstream Keycloak with longer support windows and the same surface area; advice here applies to both unless explicitly noted.1112**Siblings in the `k8s` plugin.** Keycloak is almost always deployed behind13something and packaged by something, and both are common failure sources rather14than Keycloak bugs: reverse-proxy headers, TLS termination and rate limiting in15front of it are **`traefik-hardening`** (a Keycloak that emits wrong issuer URLs16or loops on redirect is usually a proxy-header problem, not an IAM one);17authoring or fixing the chart that deploys it is **`helm`**; running it under18OpenShift's SCC and arbitrary-UID constraints is **`openshift-app`**.1920## How to use this skill2122Keycloak is a large product. Don't try to load everything — route to one or two reference files based on what the user is asking, then go deep.2324```25references/26├── server-config.md → CLI/env vars, kc.sh, kcadm.sh, hostname, db, cache,27│ TLS/proxy, logging, features, bootstrap-admin, KCRAW_28├── k8s-deployment.md → Operator install, Keycloak CR, KeycloakRealmImport,29│ raw manifests, HA topology, probes, autoscaling30├── security-hardening.md → Realm policies, brute force, FGAP v2, client policies31│ (FAPI/OAuth 2.1), DPoP, redirect URI safety, recent CVEs32├── integration.md → OIDC/SAML flows, IdP brokering, LDAP/AD federation,33│ themes, SPIs, admin clients (Java/JS/kcadm/Terraform)34├── observability.md → Metrics, OTLP tracing, structured logging, health35│ probes, troubleshooting recipes36├── upgrade-and-backup.md → Upgrade matrix, zero-downtime patches, realm37│ export/import, DB backup, disaster recovery38└── legacy-and-migration.md → Identifying/operating old versions (16.x–23.x,39 WildFly "-legacy" builds), per-version breaking-40 change ladder 16→current, and verifying a migrated41 realm/clients against the pre-migration export42 (normalize+diff recipe + expected-diffs allowlist)43```4445**Routing cheatsheet:**4647| User question | Read first |48|----------------------------------------------------------------------------|---------------------------------------|49| "How do I set the hostname / proxy / DB?" | `server-config.md` |50| "Why is my login redirecting in a loop?" | `server-config.md` (hostname/proxy) |51| "How do I deploy Keycloak on Kubernetes?" / "Operator vs raw manifests" | `k8s-deployment.md` |52| "Show me a Keycloak CR" / "What goes in `spec.*`?" | `k8s-deployment.md` + `assets/examples/` |53| "How do I do zero-downtime upgrades?" | `k8s-deployment.md` + `upgrade-and-backup.md` |54| "Harden my realm" / "FAPI / OAuth 2.1 / DPoP / FGAP" | `security-hardening.md` |55| "Recent CVEs?" / "Is 26.x.y vulnerable?" | `security-hardening.md` §CVE table |56| "Wire my SPA / mobile app / service to Keycloak" | `integration.md` |57| "Configure SAML / OIDC IdP / LDAP / social login" | `integration.md` |58| "Custom theme / authenticator / event listener / mapper" | `integration.md` §SPIs / §Themes |59| "What metrics / Grafana dashboard / Prometheus / OTLP" | `observability.md` |60| "Realm export gotchas" / "How do I back up?" / "Liquibase migration stuck" | `upgrade-and-backup.md` |61| "What version is this old instance?" / anything on WildFly / `-legacy` / `/auth` paths / 16.x–23.x | `legacy-and-migration.md` §1–2 |62| "We're on 1x.x — what breaks if we upgrade?" | `legacy-and-migration.md` §3 |63| "Did the migration keep all realm/client settings?" / compare realm before vs after | `legacy-and-migration.md` §4 + `assets/compare/` |6465**When in doubt about a CLI flag**, the source of truth is `https://www.keycloak.org/server/all-config` (full option index). When in doubt about a CR field, the source of truth is the CRD YAML in `keycloak-k8s-resources` at the version tag (see §"Authoritative sources" below).6667## Version map (May 2026)6869Latest stable: **Keycloak 26.7.0** (released 2026-07-09). The 26.6 line continued to **26.6.4** (2026-06-26) and both 26.6.3 and 26.6.4 are security batches, so anything still on 26.6.0–26.6.2 is missing fixes. Do **not** quote specific CVE IDs from memory; pull the authoritative list with `gh api repos/keycloak/keycloak/security-advisories` and `gh release view <version> --repo keycloak/keycloak --json body`. **The feed alone is not enough** — its `first_patched_version` fields are empty, so read the candidate releases' notes to learn which version actually closes a CVE. See `security-hardening.md` §CVE table for how to surface the current set.7071Notable changes through the 26.x line:7273| Version | Key changes (operator-relevant) |74|----------|------------------------------------------------------------------------------------------------|75| 26.7.x | SCIM user provisioning (**preview**), multi-cluster HA **without external caches** (preview — supersedes the external-Infinispan topology this skill documents), OpenID Shared Signals Framework (experimental), Identity Brokering API **V2** (V1 deprecated, still default-on), step-up auth for SAML clients, HAProxy + Traefik reverse-proxy blueprints. *Not yet researched at reference depth — read the release notes* |76| 26.6.x | Workflows, JWT Authorization Grant, Federated client auth, **Zero-downtime patch updates**, KCRAW_ env prefix, automatic K8s truststore, graceful HTTP shutdown, configurable Service name/port in Operator, organization groups, sensitive-info redaction in HTTP access logs. **26.6.2, 26.6.3 and 26.6.4 are all security batches**; 26.6.4 (2026-06-26) is the last 26.6 patch seen — see the advisory feed for the CVE sets they close |77| 26.5.x | Token Exchange Standard (RFC 8693) GA, declarative-user-profile GA, FIPS via Bouncy Castle, ECC keys default for new realms, Java 25 added (server image still on JDK 21 for FIPS) |78| 26.4.x | hostname-v2 GA + `hostname-v1` removed, Quarkus 3.20 LTS, `--proxy-headers` (replaces `--proxy edge|reencrypt`) |79| 26.3.x | Organizations GA, Account Console v3 GA, Admin UI on PatternFly React, FGAP v2 preview |80| 26.2.x | OIDC client policies v2, persistent user sessions GA |81| 26.0.0 | `KEYCLOAK_ADMIN` removed → `KC_BOOTSTRAP_ADMIN_*`, persistent user sessions on by default, hostname-v2 by default |8283Always cross-check with the release-notes body via `gh release view <tag> --repo keycloak/keycloak --json body --jq '.body'`. Do not rely solely on this table — bump the date and verify.8485## Authoritative sources8687When the user asks about something specific, prefer these sources over generic recall. Cited paths are relative to repo root — access via a local clone, or fetch via `gh api repos/<owner>/<repo>/contents/<path>`.8889- **Upstream source** (`keycloak/keycloak`)90 - CLI option mappers: `quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/*PropertyMappers.java`91 - Option definitions (descriptions, defaults): `quarkus/config-api/src/main/java/org/keycloak/config/*Options.java`92 - Operator Java code + CRD generators: `operator/src/main/java/org/keycloak/operator/`93 - Themes (login/email/account/admin): `themes/src/main/resources/theme/` + `js/apps/{account-ui,admin-ui}/`94 - asciidoc guides operators rarely think to read: `docs/guides/server/*.adoc`, `docs/guides/operator/*.adoc`, `docs/guides/high-availability/*.adoc`9596- **Operator install manifests** (`keycloak/keycloak-k8s-resources`)97 - Each Keycloak version has a git tag (e.g. `26.7.0`) with the manifests under `kubernetes/`. Through 26.6 that was three files; **26.7+ has more, and the operator crash-loops (`Couldn't start informer for keycloaksamlclients...`) if the extra CRDs aren't applied** — vendor the whole directory listing, not a remembered file list:98 - `keycloaks.k8s.keycloak.org-v1.yml` — the `Keycloak` CRD99 - `keycloakrealmimports.k8s.keycloak.org-v1.yml` — the `KeycloakRealmImport` CRD100 - `keycloakoidcclients…`/`keycloaksamlclients….yml` (26.7+) — client CRDs, required by the operator even if unused101 - `kubernetes.yml` — Operator Deployment + RBAC + ServiceAccount (assumes install namespace `keycloak`; one hardcoded ClusterRoleBinding subject — use the docs' kustomize-overlay approach for other namespaces)102 - `kustomization.yml`, `cluster-wide/` (26.7+)103 - The repo's `main` branch has only the README + LICENSE; the actual manifests live in tags. Use `git checkout <tag>` against a clone, or `gh api repos/keycloak/keycloak-k8s-resources/contents/kubernetes?ref=<tag>` to fetch raw. (Do NOT assume the repo is stale just because `main` looks empty.)104105- **Online docs**: `https://www.keycloak.org/`106 - `/server/all-config` — every CLI option, every default107 - `/server/configuration` — concept overview, build-time vs runtime108 - `/operator/installation`, `/operator/basic-deployment`, `/operator/advanced-configuration`, `/operator/realm`, `/operator/keycloak-cr`, `/operator/rolling-updates`109 - `/high-availability/` — multi-cluster, external Infinispan, CloudNativePG recipe110 - `/securing-apps/` — OIDC layers, SAML, DPoP, JWT-Authz-Grant, MCP authorization server (CIMD)111 - `/docs/<version>/server_admin/` and `/docs/<version>/server_development/` — admin and SPI guides112113- **gh CLI** (per the user's `gh-cli-preferred` rule):114 - Release notes: `gh release view <tag> --repo keycloak/keycloak --json body --jq '.body'`115 - Issues: `gh issue view <N> --repo keycloak/keycloak`116 - Search: `gh search issues --repo keycloak/keycloak "<query>"`117 - Security advisories: `gh api repos/keycloak/keycloak/security-advisories`118119When the user references a specific behavior, *check the source clone or CRD YAML before answering* — option names, defaults, and JSON field names get renamed across releases (e.g. `--proxy edge|reencrypt|passthrough` → `--proxy-headers xforwarded|forwarded` in 26.4; `KEYCLOAK_ADMIN` → `KC_BOOTSTRAP_ADMIN_*` in 26.0; the `hostname-v2` rewrite). Stale answers are worse than "let me check."120121## Production guardrails (the checklist that matters)122123These are the things that bite operators most. Don't suggest a Keycloak deployment that violates them without a stated reason.1241251. **Run `kc.sh build` before `start --optimized`** in a custom image, or set `spec.startOptimized: true` only after baking the build. Otherwise startup pays the auto-build cost on every pod start.1262. **`--hostname` must be a real, externally-resolvable URL** in production, with `--hostname-strict=true` (default). Do not run with `hostname-strict=false` outside of dev — it lets clients dictate the issuer.1273. **`--proxy-headers=xforwarded|forwarded` is required** when behind any reverse proxy that does TLS termination or rewrites the Host header. Pair with `--proxy-trusted-addresses` to a CIDR that covers the proxy. Without this, login redirects loop.1284. **Use `--db postgres` (or another supported vendor)**. The default `dev-file` H2 is **not** for production and silently disables clustering. Postgres is the only DB that gets tested under load by upstream.1295. **Use a real container image registry / pin a tag** (`quay.io/keycloak/keycloak:26.7.0`), never `latest`. The `nightly` tag is for CI only.1306. **Probes go to the management port (default 9000)**: `/health/started`, `/health/live`, `/health/ready` — not the main HTTP port. As of 26.6, probes return UP during DB migrations so Liquibase can finish without K8s killing the pod.1317. **HPA on Keycloak is a trap.** Sessions live in clustered Infinispan caches; scaling out and back in churns the cache. Run a fixed number of replicas (≥3 for HA) with a `PodDisruptionBudget`, not an HPA.1328. **Realm exports are NOT backups.** They omit secrets, federated users, and event history. The Postgres database is the source of truth — back that up with WAL archiving (CloudNativePG, Crunchy, RDS automated backups).1339. **Bootstrap admin is temporary.** `KC_BOOTSTRAP_ADMIN_USERNAME`/`PASSWORD` exists only to create the first real admin via `kcadm.sh`, then should be removed. The bootstrap admin auto-expires after 120 minutes.13410. **Pin Keycloak ↔ Operator versions together.** The operator at tag `26.7.0` is meant to manage Keycloak `26.7.0`. Mixing major.minor versions across the operator/server boundary is unsupported and often breaks the CRD schema.135136## Quickstart: the smallest production-shaped Keycloak137138When the user says "just stand one up so I can play," **don't** point them at `kc.sh start-dev` if they care about production fidelity — show them this instead. It's a Keycloak CR + Postgres + Ingress, no extra moving parts:139140```yaml141# 1. Install the operator (do this once per cluster)142# kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.7.0/kubernetes/keycloaks.k8s.keycloak.org-v1.yml143# kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.7.0/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml144# kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.7.0/kubernetes/kubernetes.yml145146# 2. Create a TLS secret (cert-manager / hand-roll / etc.)147# 3. Create DB credentials secret (keys: username, password)148# 4. Deploy Keycloak149apiVersion: k8s.keycloak.org/v2alpha1150kind: Keycloak151metadata:152 name: keycloak153 namespace: iam154spec:155 instances: 2156 image: quay.io/keycloak/keycloak:26.7.0157 startOptimized: false # set true when the image is pre-baked with `kc.sh build`158 hostname:159 hostname: https://auth.example.com160 strict: true161 proxy:162 headers: xforwarded163 http:164 tlsSecret: keycloak-tls # remove and use ingress for edge termination165 db:166 vendor: postgres167 host: postgres-rw.iam.svc168 database: keycloak169 usernameSecret: { name: keycloak-db, key: username }170 passwordSecret: { name: keycloak-db, key: password }171 bootstrapAdmin:172 user:173 secret: keycloak-bootstrap-admin # keys: username, password174 update:175 strategy: Auto # zero-downtime patch updates (26.6+)176 features:177 enabled: ["organizations", "admin-fine-grained-authz:v2"]178```179180See `assets/examples/` for fuller examples (with HA tuning, `KeycloakRealmImport`, raw-manifest deployment without the operator, network policies).181182## Common operator pitfalls183184- **"What version am I running?" from inside a realm** — deliberately absent from every UI except master's *Server info* page (version disclosure = hardening). Non-master admins with `manage-realm` get the version from `GET /admin/serverinfo` (26.5.4+): `kcadm.sh get serverinfo | jq -r .systemInfo.version` — note kcadm's `--fields` is useless here (it trims `systemInfo` but still dumps the ~0.5 MB remainder of the document); the `view-system` workaround was removed in 26.7. Infra-side: image tag, boot log line, or `kcadm.sh help | head -1` in the pod.185- **"What's the admin URL for realm X?"** — not discoverable in the UI. Pattern: `/admin/{login-realm}/console/#/{managed-realm}` — the first segment picks which realm *authenticates* you (its `security-admin-console` client), the fragment picks the realm being *edited*. Realm-local admins: `/admin/<their-realm>/console/` (no realm dropdown, their realm only). Master admins: `/admin/master/console/#/<realm>`. End-user self-service: `/realms/<realm>/account/`. Prefix all with the server's relative path (`/auth` on legacy-layout installs).186187- **`hostname-strict=false` "fixes" my login** → No, it papers over a proxy/headers misconfig. Set `--proxy-headers` correctly instead.188- **"Why is my custom theme not appearing?"** → The theme must be baked into a custom image and `kc.sh build` re-run; dropping a theme jar into a stock image at runtime no longer works. Themes are packaged into the optimized server jar.189- **"Operator pod logs are flooded with warnings"** → On 26.6.0 exactly, that's `#47872`, fixed in 26.6.1. Upgrade to the latest 26.6.x.190- **"Realm import keeps failing on existing realm"** → `KeycloakRealmImport` is *create-or-replace*; it won't merge. Use `kcadm.sh` for incremental edits, or accept the realm-as-config GitOps trade-off.191- **"My JS admin client is broken on 26.6.0"** → 26.6.0 shipped a broken `@keycloak/keycloak-admin-client` package, fixed in 26.6.1. Use the latest 26.6.x.192- **"Liquibase changelock stuck"** → A previous migration crashed mid-flight. `DELETE FROM DATABASECHANGELOGLOCK WHERE ID=1` (after confirming no other instance is migrating). See `observability.md` §troubleshooting for the safe procedure.193- **"I'm getting `KC_*` substitution surprises with `$` in passwords"** → Use the new `KCRAW_*` prefix (26.6+) instead of `KC_*` for any value that contains `$`.194195## Style196197- When citing options, give the **exact** flag *and* the env var: ``--proxy-headers / `KC_PROXY_HEADERS` ``. Operators copy-paste; getting it half right costs them an hour.198- Prefer linking to the source file or release-notes URL the user can verify, over restating from memory. Releases between 26.0 and 26.6 renamed flags and removed others; "I'm pretty sure" is wrong about half the time.199- When the user is on a pre-26 version, surface the upgrade urgency *briefly* (a one-liner pointing at the upgrade guide). Don't lecture.200- The user is sophisticated — skip the "Keycloak is an open-source IAM solution from Red Hat" preamble. Get to the answer.