Upgrades & Version Management¶
The operator uses a KeycloakVersionMap CRD to map Keycloak version strings
and aliases to container images. Understanding version resolution and how
the operator orchestrates same-minor vs. cross-minor changes is essential
before performing an upgrade.
How version resolution works¶
When the operator reconciles a KeycloakInstance, it resolves spec.version
through this priority chain:
spec.image(escape hatch) — if set, this exact image is used directly, bypassingKeycloakVersionMapresolution entirely.status.observedVersionbecomes the tag portion of this value.KeycloakVersionMap/default— the operator always looks up the cluster-scopedKeycloakVersionMapnameddefault.- Alias resolution — a value like
"latest","stable", or"26"resolves throughspec.aliasesto a key present inspec.versions. This is a single alias hop followed by an exact-key match — there is no prefix/fuzzy matching (unlikebnerd-gitlab-operator's resolver). - Exact match —
"26.6.4"matches the entry directly.
If the version map is not present, or the requested version/alias is not
(yet) found in it, resolution is transient: the instance stays Pending
with condition reason VersionResolutionPending and requeues after 30
seconds — the map may simply not be applied yet, or may be updated later to
add the version.
The default KeycloakVersionMap¶
examples/versionmap-default.yaml ships the qualified 26.x ladder:
apiVersion: k8s.bnerd.com/v1alpha1
kind: KeycloakVersionMap
metadata:
name: default
spec:
versions:
"26.5.7":
image: quay.io/keycloak/keycloak:26.5.7
"26.6.4":
image: quay.io/keycloak/keycloak:26.6.4
"26.7.0":
image: quay.io/keycloak/keycloak:26.7.0
aliases:
latest: "26.7.0"
stable: "26.6.4"
"26": "26.6.4"
"26.5": "26.5.7"
"26.6": "26.6.4"
"26.7": "26.7.0"
The Helm chart installs this same content automatically
(versionMap.installDefault: true, the default) — see
Installation § Applying the default KeycloakVersionMap.
Same-minor vs. cross-minor: how an upgrade is applied¶
The operator inspects the running vs. target Keycloak version and picks one of two upgrade strategies:
| Change | Strategy | Availability |
|---|---|---|
Same minor (e.g. 26.6.3 → 26.6.4) |
Plain one-by-one rolling update on the StatefulSet | Stays available throughout |
Cross-minor or major (e.g. 26.6.4 → 26.7.0) |
Drain-first recreate: scale to zero, then bring the new version up | Full outage for the duration |
A cross-minor change never runs mixed versions — the operator always scales
the old version to zero before starting the new one, reporting the
transition via the UpgradeInProgress condition and a UpgradeCompleted
Event once done.
A cross-minor upgrade is a full outage, with no automatic rollback
Because a cross-minor change drains to zero before the new version
starts, the instance is unavailable for the duration. If the new version
fails to come up, the instance stays down — there is no automatic
rollback. Recovery is to fix the target version (correct spec.version,
or set the break-glass annotation below and point back at the previous
version) and let the operator recreate.
Ready and status.observedVersion only advance once the StatefulSet
rollout has fully converged onto the new revision (updatedReplicas >=
spec.replicas, updateRevision == currentRevision, observedGeneration
caught up) — so readiness is never reported while old-revision pods still
serve.
Performing an upgrade¶
Upgrades are declarative: update spec.version on the KeycloakInstance.
kubectl get kci my-keycloak -n my-keycloak
# NAME PHASE HOST VERSION AGE
# my-keycloak Ready auth.example.com 26.6.4 30d
kubectl patch kci my-keycloak -n my-keycloak \
--type merge \
-p '{"spec":{"version":"26.7.0"}}'
kubectl get kci my-keycloak -n my-keycloak -w
# NAME PHASE HOST VERSION AGE
# my-keycloak UpgradeInProgress auth.example.com 30d
# my-keycloak Ready auth.example.com 26.7.0 30d
Check status.observedVersion matches spec.version once done:
No downgrades (with a break-glass override)¶
A downgrade is refused by default: the instance is set to phase:
Failed, condition reason VersionDowngradeBlocked, and the running
workload is left untouched (not scaled down, not recreated).
To force a downgrade in an emergency, set the break-glass annotation:
kubectl annotate kci my-keycloak -n my-keycloak \
k8s.bnerd.com/allow-downgrade="true" --overwrite
kubectl patch kci my-keycloak -n my-keycloak \
--type merge \
-p '{"spec":{"version":"26.6.4"}}'
An allowed cross-minor downgrade is itself performed as a drain-first recreate (same outage profile as a forward cross-minor upgrade) — never a live mixed-version swap.
Customising the version map¶
To qualify a new Keycloak version:
Add the entry under spec.versions:
Update the relevant alias if appropriate:
An alias pointing at a version not present in spec.versions is rejected at
apply time (the error message lists the known versions). Editing the map
does not automatically trigger a reconcile on running instances —
version resolution only re-runs when the KeycloakInstance spec changes.