The Keycloak Upgrade Ledger

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

2026-08-25 · run 2026-08-25-25.0.6-to-26.0.0-1

25.0.6 → 26.0.0

Upgrade
25.0.6 → 26.0.0
Scale
1000 users · 2 realms (master + lab) · 10 clients · 1 group · 8 realm roles
Database
PostgreSQL 16.15 (Debian pgdg13), single node, container
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 1 of 3 (target: 3 clean, ≥1 with rollback exercised) — this lab holds a path to three clean runs, one exercising rollback
Condition
routine — no adverse condition applied
Elapsed
14s total, container stop → first token issued

Environment

FieldValue
Keycloak from → to25.0.6 → 26.0.0
Stepping path (if multi-hop)none — direct
Distributionquay.io/keycloak/keycloak (official image), 680MB → 691MB
Start modestart --http-enabled=true w/ external DB
DatabasePostgreSQL 16.15 (Debian pgdg13), single node, container
Postgres tuningstockshared_buffers 128MB, maintenance_work_mem 64MB, statement_timeout 0. Added retroactively 2026-08-25 to close a gap in our own records: read from the same unmodified postgres:16 image on the same box. The compose db service sets no tuning, so stock is guaranteed by the file, not remembered.
Dataset scale1000 users · 2 realms (master + lab) · 10 clients · 1 group · 8 realm roles
Seeding methodpartialImport, batch size 2000 (one batch), 25.2s wall clock
Fixture profiletypical — auth-flows, password-policy, lifespans, clients, client-scopes, themes, groups-roles, events, required-actions
Topologysingle container, single replica
HostHetzner CCX33 (fsn1) — 8 vCPU dedicated, 30GB usable RAM, 226GB local NVMe, Debian 13, kernel 6.12.101+deb13-cloud-amd64
JVM heap-Xms1g -Xmx4g
Elapsed clock14s total, container stop → first token issued

Procedure

./bin/lab-up.sh 25.0.6
./bin/seed-realm.sh --profile typical --users 1000
./bin/snapshot.sh baseline-25.0.6-typical-1k
./bin/upgrade.sh 26.0.0

Timings

PhaseDurationNotes
Container start → DB connect~3sfrom stop to config load at 15:31:36
Liquibase / schema migration~0.8s15:31:39,198 "Updating database" → 15:31:39,992 "Migrating older model to 26.0.0"; runs on (main), i.e. before the server reports ready
Cache/realm warmnot separately measuredunknown
First successful token issuewithin 14s of stopwait-ready.sh poll granularity is 3s, so this bounds it rather than measures it
Total to ready14s

At this scale the migration is not the cost — process start is. The 14s is dominated by JVM/Quarkus startup, not by schema work. This number's only use is as the 1k anchor for a scale comparison; it says nothing about a real realm.

Outcome

Breakage observed

Hostname v1 option rejected at ERROR level, but startup continues

Two JDBC resource leak warnings during migration

Rollback

Verification

ClaimPrimary sourceChecked
KC_HOSTNAME_STRICT_HTTPS removed / v1 in KC26Keycloak 26 upgrade guide — NOT YET CHECKED
Admin env var rename KEYCLOAK_ADMIN*KC_BOOTSTRAP_ADMIN_*not exercised — compose sets both, so this run cannot confirm it

This record is not publishable until the two rows above are filled. The compose file deliberately sets both admin env var schemes, which means this run says nothing about the rename that the version matrix lists as the headline breaking change for this path. A run that isolates it is still needed.

Publishable extract

Withheld pending the Verification table above.

← All runs