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
| Field | Value |
|---|---|
| Keycloak from → to | 25.0.6 → 26.0.0 |
| Stepping path (if multi-hop) | none — direct |
| Distribution | quay.io/keycloak/keycloak (official image), 680MB → 691MB |
| Start mode | start --http-enabled=true w/ external DB |
| Database | PostgreSQL 16.15 (Debian pgdg13), single node, container |
| Postgres tuning | stock — shared_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 scale | 1000 users · 2 realms (master + lab) · 10 clients · 1 group · 8 realm roles |
| Seeding method | partialImport, batch size 2000 (one batch), 25.2s wall clock |
| Fixture profile | typical — auth-flows, password-policy, lifespans, clients, client-scopes, themes, groups-roles, events, required-actions |
| 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 |
| JVM heap | -Xms1g -Xmx4g |
| Elapsed clock | 14s 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
| Phase | Duration | Notes |
|---|---|---|
| Container start → DB connect | ~3s | from stop to config load at 15:31:36 |
| Liquibase / schema migration | ~0.8s | 15: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 warm | not separately measured | unknown |
| First successful token issue | within 14s of stop | wait-ready.sh poll granularity is 3s, so this bounds it rather than measures it |
| Total to ready | 14s |
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
- Reached ready
- Realm config intact — users 1000, clients 10, roles 8, auth flows 9, all unchanged across the upgrade
-
Login flow works —
labuser7issued a token from thelabrealm post-upgrade - Custom SPI loaded — none in fixture, not exercised
- Themes render — theme names set, theme files not mounted; not exercised
-
Federation/brokering intact — not in
typicalprofile
Breakage observed
Hostname v1 option rejected at ERROR level, but startup continues
- Symptom:
ERROR [org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers] (main) Hostname v1 options [hostname-strict-https] are still in use, please review your configuration - Evidence: container log 2026-08-25 15:31:36,186. Also
WARN ... The following run time options were found, but will be ignored during build time: kc.spi-hostname-v2-hostname-strict - Cause: confirmed —
KC_HOSTNAME_STRICT_HTTPSis a hostname-v1 option; Keycloak 26 moved to hostname v2. The lab's compose file sets it for cross-version compatibility. - Fix or workaround: drop
KC_HOSTNAME_STRICT_HTTPSfor 26+. It is a no-op there. - Would this hit a customer? Yes, and it is config-dependent, not scale-dependent. Anyone carrying a 25-era hostname config into 26 hits this. It logs at ERROR, which will trip log-based alerting and read as a failed upgrade to an operator watching the console — while the server is in fact healthy. That gap between "looks failed" and "is fine" is the operationally expensive part.
Two JDBC resource leak warnings during migration
- Symptom:
WARN [io.agroal.pool] Datasource '<default>': JDBC resources leaked: 3 ResultSet(s)and16 ResultSet(s) - Evidence: log 15:31:39,738 and 15:31:40,957, both during the Liquibase phase
- Cause: suspected — Liquibase/Agroal interaction during schema update. Not investigated.
- Fix or workaround: none applied; no observed effect.
- Would this hit a customer? Almost certainly universal on this path, and almost certainly cosmetic. Worth knowing only so it is not mistaken for the cause of a real failure.
Rollback
- Exercised this run? no
- Method: snapshot taken (
baseline-25.0.6-typical-1k, 500K) but not restored - Time to restore: unknown
- Data loss / divergence: unknown
- Verdict: not yet established for this path. The 3-clean-runs gate requires at least one rollback; this run does not satisfy it.
Verification
| Claim | Primary source | Checked |
|---|---|---|
KC_HOSTNAME_STRICT_HTTPS removed / v1 in KC26 | Keycloak 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.