26.0.0 → 26.7.1 (rollback exercised)
- Upgrade
- 26.0.0 → 26.7.1
- Scale
- 1000 users, typical profile, 2 realms, 10 clients, 8 roles
- Database
- Postgres 16.15 single node
- Topology
- single container, single replica
- Host
- Hetzner CCX33 (fsn1) — 8 vCPU dedicated, 30GB usable RAM, 226GB local NVMe, Debian 13, kernel 6.12.101+deb13-cloud-amd64
- Rehearsal
- attempt 3 of 3 — this is the rollback run the gate requires — this lab holds a path to three clean runs, one exercising rollback
- Condition
- routine — no adverse condition applied
Environment
Identical to attempts 1 and 2: Hetzner CCX33 (fsn1), 8 vCPU dedicated, 30GB RAM,
local NVMe; Postgres 16.15 single node; 1000 users, typical profile, 2 realms,
10 clients, 8 roles; JVM -Xms1g -Xmx4g; single container.
Procedure
restore baseline-26.0.0-typical-1k # identical starting state
upgrade 26.0.0 -> 26.7.1, wait ready
POST /admin/realms/lab/users {canary-postupgrade} # data written AFTER the snapshot
# --- roll back ---
stop keycloak; KC_VERSION=26.0.0
dropdb / createdb / pg_restore baseline-26.0.0-typical-1k
start keycloak 26.0.0, wait ready
The canary is the point of the run. Anything written between the snapshot and the rollback decision is what a customer actually loses, and asserting that it disappears is the only way to know the rollback is real rather than nominal.
Timings
| Phase | Duration |
|---|---|
DB drop + create + pg_restore | 2.475s |
| Total, rollback decision → serving traffic again | 15.9s |
The restore is 2.5s of the 15.9s. The rest is Keycloak process start, which is the same fixed cost seen everywhere else in these runs.
Outcome
- Reached ready on 26.0.0 after rollback
-
serverinforeports 26.0.0 -
MIGRATION_MODELback to 26.0.0 — schema genuinely reverted, not just the binary - 1000 users present
-
End-user login works (
labuser7issued a token) - Canary absent — the post-upgrade user is gone, as it must be
Breakage observed
None. The rollback was clean.
Rollback
NOTE added 2026-08-25: this run performed the restore steps by hand with
docker compose up -d --force-recreate.bin/restore.shas shipped at the time useddocker compose start, which reuses the existing container and silently re-migrates — see2026-08-25-backup-and-rollback-tooling. The finding below holds for the procedure tested; the script did not implement it.
- Exercised this run? yes — this is the gate run
- Method: stop Keycloak,
pg_restorethe pre-upgrade snapshot, start the old image - Time to restore: 2.475s DB, 15.9s to serving (1k users)
- Data loss / divergence: Total, for everything written after the snapshot. Confirmed by the canary. This is not a downgrade — Keycloak has no schema downgrade path — it is a point-in-time restore, and it takes the data with it.
- Verdict: Rollback is genuinely available on 26.0.0 ↔ 26.7.1, with a hard caveat. It is available as restore, not as downgrade. Availability is therefore a property of the customer's snapshot discipline and their tolerance for losing the write window, not a property of Keycloak.
Verification
| Claim | Primary source | Checked |
|---|---|---|
| Schema reverted, not just the binary | MIGRATION_MODEL read from Postgres directly = 26.0.0 | ☑ |
| Post-snapshot writes are lost | canary user, asserted absent after rollback | ☑ |
| Keycloak has no supported schema downgrade | not verified against docs — inferred from the restore being required at all | ☐ |
| Holds at scale | not tested — 2.5s restore is a 500K dump | ☐ |
Publishable extract
Rolling back a Keycloak upgrade is a database restore, and it costs you every write since the snapshot.
On a 1,000-user realm (Postgres 16.15, Hetzner CCX33 — 8 vCPU dedicated, 32 GB, local NVMe), rolling 26.7.1 back to 26.0.0 took 15.9 seconds end to end. Only 2.5s of that was the
pg_restore; the rest was Keycloak starting.We verified it is a real rollback and not a cosmetic one:
MIGRATION_MODELin Postgres went back to 26.0.0, so the schema itself reverted. We also created a user after the upgrade and confirmed it was gone afterwards — because that is the part people miss. There is no schema downgrade. You are restoring a point-in-time backup, so every registration, password change, session and admin edit between your snapshot and your rollback decision is discarded.The practical consequence: your rollback window is not bounded by how long the restore takes. It is bounded by how much write activity you can afford to throw away. Decide that number before the maintenance window, because during one you will not want to.
Publishable. The 15.9s figure is honest but must always be quoted with its 1,000-user scale attached — the restore half scales with the dump, the startup half does not.