The Keycloak Upgrade Ledger

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

2026-08-25 · run 2026-08-25-s12-postgres-major-floor

S12: a Keycloak *minor* upgrade raised the minimum Postgres major

Upgrade
26.0.0 → 26.7.1
Scale
202 users, 2 realms (fresh install + seed per major)
Database
PostgreSQL 13.23 / 12.22 / 11.16, single node, container — the variable
Topology
single container
Host
Hetzner CCX33 (fsn1) — 8 vCPU dedicated, 30 GB RAM, local NVMe
Condition
Older Postgres major

Summary

Keycloak 26.0.0 runs on PostgreSQL 12. Keycloak 26.7.1 refuses to. The minimum supported Postgres major moved from 12 to 13 inside major version 26 — across what looks, from the version number, like a routine minor bump.

ERROR: Persistence unit 'keycloak-default' was configured to run with a database
version of at least '13.0.0', but the actual version is '12.22.0'.

The upgrade fails before the migration. Nothing is applied, no data is lost, and the fix is a database upgrade — which is a different project, a different change window and usually a different team.

Environment

FieldValue
Keycloak from → to26.0.0 → 26.7.1
Distribution / start modequay.io/keycloak/keycloak official image, start w/ external DB
DatabasePostgreSQL 13.23 / 12.22 / 11.16, single node, container — the variable
Postgres tuningstock defaults for each major
Adverse scenarioS12
Dataset scale202 users, 2 realms (fresh install + seed per major)
Seeding methodpartialImport (bin/seed-realm.sh --profile typical --users 200)
Topologysingle container
HostHetzner CCX33 (fsn1) — 8 vCPU dedicated, 30 GB RAM, local NVMe
JVM heap-Xms1g -Xmx4g

Each major started from an empty pgdata volume: fresh install at 26.0.0, seed, then the upgrade under test.

Results

PostgresKeycloak 26.0.0Keycloak 26.7.1
13.23installs, ready 19supgrade READY in 16s — model 26.7.1, changelog 211, 202 users, login 200
12.22installs, ready 19s, changelog 144, 202 users seededREFUSES TO START — requires ≥ 13.0.0
11.16REFUSES TO START — requires ≥ 12.0.0not reached

So the floors are:

KeycloakMinimum Postgres major
26.0.012
26.7.113

The failure is clean

After 26.7.1 refused to start on Postgres 12.22:

migration_model26.0.0 — unchanged
DATABASECHANGELOG144 — unchanged
Users202 — intact
Containerexited 1

Fail-closed, like S3, S4 and S5 before it. The database is exactly as it was and rolling back means putting the old image tag back.

The escape hatch, and its wording

Both versions offer one, and they do not name the same property:

Refusing versionSuggested override
26.7.1 on PG 12jakarta.persistence.database-product-version=12.22.0
26.0.0 on PG 11quarkus.datasource.db-version=11.16.0

Both append the same warning: "but this may disable some features and/or impact performance negatively". We did not test either override. It is a documented way to start on an unsupported database; it is not a supported configuration, and a rescue engagement should treat it as a bridge to a database upgrade rather than a destination.

Verification

ClaimPrimary sourceChecked
26.7.1 upgrades cleanly on PG 13.23run: READY 16s, model 26.7.1, changelog 211, login 200
26.0.0 installs and serves on PG 12.22run: ready 19s, changelog 144, 202 users seeded
26.7.1 refuses on PG 12.22at least '13.0.0', but the actual version is '12.22.0'
26.0.0 refuses on PG 11.16at least '12.0.0', but the actual version is '11.16.0'
The refusal applies nothingchangelog 144, model 26.0.0, 202 users after the failure
Which 26.x release moved the floornot bracketed — only known to be in (26.0.0, 26.7.1]
Whether the override flags worknot tested — deliberately
Whether PG 13 is the floor for later Keycloaknot tested — the floor evidently moves, so this expires
Managed-service equivalents (RDS/Cloud SQL/Azure majors)not tested — same engine versions, different upgrade mechanics

Publishable extract

Keycloak 26.0.0 runs on PostgreSQL 12. Keycloak 26.7.1 does not. Same major version of Keycloak.

We installed Keycloak 26.0.0 on Postgres 12.22, seeded a realm, and upgraded to 26.7.1 — the kind of bump most change advisory boards would wave through as a minor. It stopped before it started:

ERROR: Persistence unit 'keycloak-default' was configured to run with a database
version of at least '13.0.0', but the actual version is '12.22.0'.

The floors we measured:

KeycloakMinimum Postgres
26.0.012
26.7.113

On Postgres 13.23 the same upgrade took sixteen seconds and worked perfectly.

The good news is that the refusal is clean: 144 changelog rows before, 144 after, all 202 users present, container exited 1. Nothing was half-done. The bad news is what the fix is. You cannot resolve this inside the Keycloak change window — you need a database major upgrade, which is a different project with a different risk profile and usually a different team. If you discovered it during the window, the window is over.

Keycloak does offer an override (jakarta.persistence.database-product-version) and warns in the same breath that it "may disable some features and/or impact performance negatively". We deliberately did not test it. Treat it as a bridge to a database upgrade, not a destination.

The pre-flight question is one line, and almost nobody asks it, because the Keycloak version number does not suggest it needs asking:

select version();

← All runs