The PostgreSQL fleet upgrade¶
A record of the upgrade carried out in August 2026: what the fleet looked like when it was surveyed on 2026-08-23, why it was scattered, and the two traps that decided the order. The databases as they are now are in Helm releases and the manifests it links.
Why the fleet is scattered¶
Nine clusters never set imageName. CloudNativePG defaults it at creation and
writes that value into the spec permanently — it does not re-default when the
operator is upgraded. So each cluster froze whatever shipped on the day it was
created, and nobody ever chose a version. At the time of the survey every
cluster pinned its image explicitly.
The default for new clusters is set by the cnpg-database chart, and is not
restated here: it is invalidated by a change in
thaum-xyz/helm-charts, which no diff
in this repository would reveal. A version written down here said 18.6 long after
the chart had moved to 17.11. See the chart's
generated values reference
for the current cluster.imageName.
State when surveyed¶
| Cluster | Running | OS base | Latest in series | Behind |
|---|---|---|---|---|
| paperless | 15.2 (Feb 2023) | bullseye | 15.19 | ~17 patches |
| multimedia ×3 | 16.2 (tag 16.1) | bullseye | 16.15 | ~13 |
| atuin | 16.11 | bullseye | 16.15 | 4 |
| mealie | 17.2 | bullseye | 17.11 | 9 |
| grafana | 17.5 | bullseye | 17.11 | 6 |
| pocket-id | 17.5 | bullseye | 17.11 | 6 |
| photos | 17.5 (VectorChord) | bookworm | — | tied to VectorChord |
| ai-gateway | 18.1 | trixie | 18.6 | 5 |
| mended-drum | 18.1 | trixie | 18.6 | 5 |
Nothing is end-of-life. PG15 runs to Nov 2027, 16 to Nov 2028, 17 to Nov 2029, 18 to Nov 2030. The exposure is missed patches, not EOL — paperless is the outlier at roughly three and a half years of them.
Two traps that decide the order¶
pg_upgrade mounts the old binaries into the new container. A major upgrade
across mismatched OS bases fails on missing shared libraries; this already bit
the immich migration (libssl.so.1.1: cannot open shared object file) and was
only resolved by stepping through a matching base. So a major upgrade must keep
the same Debian base on both sides.
Changing the Debian base changes glibc, and glibc changes collation order.
Text indexes built under one collation are silently wrong under another. Moving
bullseye → trixie is not a free swap: it needs a REINDEX of text indexes
afterwards. This is why base changes are a separate phase rather than something
to fold into a patch bump.
Plan¶
The chart owns the version, and each cluster drops its own imageName as it is
moved onto the chart default. A pin now means "this cluster cannot follow", not
"this is what it happened to get".
17 on bookworm because it matches the VectorChord image immich needs, so photos
is not a major-version outlier. 17.x-system-bookworm does not exist; the
variants are minimal and standard.
Collation is a non-issue here. Every database in the fleet is collate=C,
which compares by byte order and does not depend on glibc, so moving between
Debian bases needs no REINDEX. Confirmed on mealie: datcollversion is null
and the only index collations are default (inheriting C) and C.
State after¶
Eight clusters follow the chart; the three that do not are recorded below with the reason.
atuin 17.11 follows chart (was 16.11, two steps)
grafana 17.11 follows chart (was 17.5)
mealie 17.11 follows chart (was 17.2)
paperless 17.11 follows chart (was 15.2, two steps, two majors)
pocket-id 17.11 follows chart (was 17.5)
prowlarr 17.11 follows chart (was 16.1, two steps)
radarr 17.11 follows chart (was 16.1, two steps)
sonarr 17.11 follows chart (was 16.1, two steps)
ai-gateway 18.1 pinned PostgreSQL has no downgrade path
mended-drum 18.1 pinned ditto
photos 17.5 pinned tensorchord VectorChord build
Each two-step move went base-first (same major, a restart) and then major (an
offline pg_upgrade with the base held constant), because pg_upgrade mounts
the old binaries into the new container and fails across mismatched
bases -- the immich libssl.so.1.1 failure. paperless spanned 15 to 17 in one
pg_upgrade, which is supported.
A backup was taken and confirmed completed before every major upgrade.
Before each upgrade¶
primaryUpdateMethod: switchoverrefuses an image change and a config change in the same step. Land them separately.- These HelmReleases take values via
valuesFroma ConfigMap. Changing the ConfigMap does not trigger an upgrade -- helm-controller only notices on its next interval. Reconcile the release explicitly (flux -n <ns> reconcile helmrelease <release>) or the image change appears to do nothing while everything reports Ready. See how Flux is layered. - pocket-id exits rather than retrying when postgres-rw blips, so it will restart during its primary's restart. It recovers on its own.
- CNPG will not shrink storage, so a live resize must be mirrored into git or the next Helm upgrade fails on the webhook.