Keycloak upgrade guide¶
This guide covers every kind of change you might need to ship for Keycloak:
- Theme changes (CSS, logo, copy)
- Keycloak version bumps (upstream)
- Keycloak CR / runtime configuration
- Realm settings (clients, flows, themes, signup)
- Rollback
- Data safety notes
All changes land in the Skatzi/platform repo. Flux on the mgmt-cluster reconciles components/keycloak/ into the keycloak namespace.
1. Theme changes¶
Any file under components/keycloak/theme/kodexet/ (CSS, logo, HTML overrides, theme.properties, translations) is baked into the Docker image at build time. Shipping a theme change is therefore a new image tag + bump on the Keycloak CR.
Workflow¶
-
Edit the theme on a branch, e.g. tweak
theme/kodexet/login/resources/css/styles.css. -
Pick the next image tag. Tags follow
vMAJOR.MINOR.PATCH. Check what's currently deployed:grep 'image:' components/keycloak/base/keycloak-instance.yaml # image: harbor.prod.skatzi.com/skatzi/keycloak-kodexet:v1.0.1Bump patch for pure theme tweaks, minor for bigger reworks.
-
Bump the image reference in
components/keycloak/base/keycloak-instance.yaml: -
Commit, push, and tag. The Gitea CI workflow
.gitea/workflows/ci-keycloak.yamltriggers on tags matchingkeycloak-v*.*.*:git add components/keycloak/ && git commit -m "fix(keycloak): <what changed>" git push origin main git tag keycloak-v1.0.2 git push origin keycloak-v1.0.2The tag push kicks Kaniko — it builds from
components/keycloak/Dockerfileand pushes:harbor.prod.skatzi.com/skatzi/keycloak-kodexet:v1.0.2harbor.prod.skatzi.com/skatzi/keycloak-kodexet:latest
-
Wait for the image to land in Harbor (check the Gitea Actions tab or Harbor UI). Flux only rolls once the image exists.
-
Flux reconciliation picks up the new
image:value within a minute and the operator rolls the StatefulSet. Watch the rollout:kubectl --context admin@mgmt-cluster -n keycloak get pod -w kubectl --context admin@mgmt-cluster -n keycloak logs keycloak-0 -fThe first boot runs
kc.sh buildagain (becausestartOptimized: false) — expect a minute of startup beforeListening on http://0.0.0.0:8080appears.
Activating the theme in a realm¶
Adding a theme to the image does not automatically assign it to a realm. Do that once in the admin console:
Admin console → select realm → Realm Settings → Themes → Login Theme:
kodexet→ Save.
The same applies for Account / Email / Admin themes if they ever get their own variants.
2. Keycloak version bump¶
Upstream releases: https://www.keycloak.org/docs/latest/release_notes/index.html. Always read the release notes for breaking changes before bumping.
Workflow¶
-
Bump the base image in
components/keycloak/Dockerfile: -
Bump the Keycloak Operator too if required by the new version — managed separately via Flux under the operator's own component directory.
-
Bump the image tag in
keycloak-instance.yaml(use a minor bump when the upstream minor changes, e.g.v1.1.0). -
Follow the rest of the theme-change workflow from step 4 onwards.
Checklist for major version bumps¶
- Read upstream release notes end-to-end
- Back up the Keycloak Postgres DB (see Data safety notes)
- Check for deprecated server options in
additionalOptionsof the CR - Verify theme
parent=keycloak.v2still resolves (the upstream theme tree is version-specific) - Validate the admin console loads and existing clients still work before closing the change
3. Keycloak CR / runtime configuration¶
Changes to components/keycloak/base/keycloak-instance.yaml do not require a new image — the operator picks up CR changes and rolls the pod on its own. No tag push needed.
Common fields¶
| Field | What it does |
|---|---|
spec.instances |
Replica count. Keep at 1 unless HA is deliberately configured. |
spec.db.* |
Database vendor/host/secret. Changing vendor or host requires a restart and is a data-migration exercise. |
spec.hostname.hostname |
Public FQDN. Must match the HTTPRoute and TLS cert. |
spec.proxy.headers |
Keep at xforwarded — Cilium Gateway terminates TLS and forwards headers. |
spec.additionalOptions |
Any Keycloak server option (kc.sh start --<name>=<value>). Build-time options force a rebuild on pod boot. |
spec.startOptimized |
Must stay false as long as additionalOptions contains build-time options, otherwise the pod crash-loops with build time options have values that differ from what is persisted. |
Ship the change the normal way:
git add components/keycloak/base/keycloak-instance.yaml
git commit -m "chore(keycloak): <what changed>"
git push origin main
Flux reconciles within a minute, the operator rolls the pod, done.
4. Realm settings¶
The realm CRDs are disaster-recovery snapshots only — they do not drive live config. Live changes must be made manually in the admin console, and the YAML in base/*-realm.yaml should be updated to match so the DR snapshot stays current.
Workflow for any realm change¶
-
Make the change in the admin console:
https://keycloak.prod.skatzi.com→ admin login → select the realm (kodexetorskatzi).- Change the setting (themes, login flows, identity providers, clients, registration, etc.).
- Save.
-
Verify end-to-end — log in with an affected client before mirroring to YAML.
-
Mirror the change into the DR snapshot in
components/keycloak/base/<realm>-realm.yaml. Example — enabling self-service registration and switching login theme: -
Commit as
chore(keycloak): update <realm> realm DR snapshotand push.
Why not drive realms from the CRD?¶
The Keycloak Operator's KeycloakRealmImport resource only imports a realm the first time — subsequent CRD edits are silently ignored against a live realm. Attempting to drive live config from YAML therefore produces silent drift. The DR snapshot exists so that if we ever had to recreate the database from scratch, kubectl apply -f kodexet-realm.yaml would rebuild the realm to the documented state.
Common realm-change pitfalls¶
- "Users get sent straight to Google" — check Authentication → Flows → Browser → Identity Provider Redirector → config. If
Default Identity Provideris set, the login page is skipped. Clear it to get the normal login form back. - "Theme changes don't apply" — make sure the theme is both in the image (step 1) and assigned on the realm (Realm Settings → Themes).
- "Google login fails after changing redirect URIs" — mirror the same URIs in the Google Cloud OAuth client, not just in Keycloak.
5. Rollback¶
Rollback an image¶
# In keycloak-instance.yaml
spec:
image: harbor.prod.skatzi.com/skatzi/keycloak-kodexet:v1.0.1 # previous tag
Commit, push — Flux rolls back to the previous image. All images stay in Harbor; nothing is garbage-collected automatically.
Rollback a CR change¶
git revert <sha> the offending commit, push, Flux reconciles the previous state.
Rollback a realm change¶
There is no automated rollback. Either:
- Undo the change manually in the admin console (preferred for small changes), or
- Restore the Postgres DB from a backup and re-import the DR snapshot (heavy, loses all user activity since the backup).
6. Data safety notes¶
Keycloak's state lives in its Postgres StatefulSet (postgres-db in the keycloak namespace) with its own PVC. It is not touched by:
- Image bumps (theme or upstream version)
- CR changes (
keycloak-instance.yaml) - Realm DR snapshot updates (the YAML only applies on initial import)
It is at risk from:
- Deleting the PVC (
persistent-volume-claim.yaml) or the Postgres StatefulSet - A Postgres version bump without a proper dump/restore
KeycloakRealmImportre-import if the operator is ever changed to reconcile continuously — check release notes before upgrading the operator
Before any major version bump or storage change, take a logical dump:
kubectl --context admin@mgmt-cluster -n keycloak exec -it postgres-db-0 -- \
pg_dumpall -U <admin-user> > keycloak-backup-$(date +%F).sql
Store the dump outside the cluster (OpenBao file path, S3, local encrypted backup) — never in the repo.