The Keycloak Upgrade Ledger

Every upgrade we have rehearsed, with the environment stated and the clock running.

2026-08-26 · run 2026-08-26-runE-stepping-ladder-21-to-26

Run E: 21.1.2 → 26.7.1 goes direct, and the ladder is one changeset longer

Upgrade
21.1.2 → 26.7.1
Scale
small — realm lab seeded on 21.1.2 via seed-realm.sh --profile typical
Database
PostgreSQL 16, single node, container, purged between the two runs
Topology
single container
Host
Hetzner CCX33 (fsn1) — 8 vCPU dedicated, 30 GB RAM, local NVMe
Condition
routine — no adverse condition applied

Summary

The five-major jump goes direct. 21.1.2 → 26.7.1 in a single hop reaches ready in 18 seconds, stamps migration_model 26.7.1, and issues tokens. No stepping required.

The six-hop ladder also works, every hop clean, and ends one changeset further along: 211 rows against the direct jump's 210. The extra one is 25.0.0-28265-index-cleanup, and the schema it leaves is equivalent.

Environment

FieldValue
Distribution / start modequay.io/keycloak/keycloak official images, start w/ external DB
DatabasePostgreSQL 16, single node, container, purged between the two runs
Postgres tuningstock
Dataset scalesmall — realm lab seeded on 21.1.2 via seed-realm.sh --profile typical
Topologysingle container
HostHetzner CCX33 (fsn1) — 8 vCPU dedicated, 30 GB RAM, local NVMe
JVM heap-Xms1g -Xmx4g
Source state21.1.2, DATABASECHANGELOG 113 rows

Fixture caveat, stated because it bounds the result. seed-realm.sh targets current admin APIs and only partially applied against 21.1.2 — the realm ended with one user rather than the profile's full set. The migration path is exercised; a rich 21.1.2 realm is not. Treat this as "the schema migrates" and not "every 21.1.2 feature survives".

Outcome

Verification

ClaimPrimary sourceChecked
21.1.2 → 26.7.1 works in one hopE0 — READY, model 26.7.1, token 200
The full ladder works, hop by hopE1 — six hops, all READY, model advancing each time
Direct ends at 210 changesets, the ladder at 211count(*) from DATABASECHANGELOG on both
The difference is 25.0.0-28265-index-cleanupcomm of the two ordered changeset-id lists
The direct result passes a full index auditbin/index-audit.sh — 118/118 correct
The two schemas are otherwise identicalnot proven — the direct DB's table/column/index counts were captured, the ladder DB was gone before its own could be. A column-by-column diff was never run
A rich 21.1.2 realm survivesnot tested — see the fixture caveat
A custom provider survives the span at runtimenot tested here; S7 covered the endpoints only
The same at 100k+ usersnot tested — README asks for two scales and this is one

← All runs