# The last six PostgreSQL apps, by day (2026-09-30) Brief: the 2026-09-30 day brief (Parts 0, A–F). Architecture read first: `architecture/09-update-architecture.md` §3 (decisions 16, 35, 37–39, 42–43), §6.4 parts 7 and 10, §6.4.2, §6.5; `07-backup-architecture.md` §6.1. Baselines (verified live 12:00): controller `d48da6c` v0.283.1 · agent `d766666` v0.138.0 · felhom.eu `2de56ce` · catalog `6446197`. **No controller, agent or hub release in this session.** ## Part 0 — Tester-2 (read only, hub `GET /configs` + `GET /hosts`, `P0/`) - The customer record `Tester-2` (sajatfelhom.hu) exists, its config is `v0.283.1 MANAGED`. - **No box has registered:** status and version read `—`, and `/hosts` lists no Tester-2 host. - So no apps and no `app_update.unattended` value exist yet to read. ## Part A — the upstream table (A1, read 2026-09-30; raw notes and URLs in `A1/upstream-notes.md`) Rule (decision 42): the newer major only when the app's own upstream runs it; else the newest major its stack documents. | app | pinned app | pinned engine | upstream engine (pinned tag / newest release) | documented range | outcome | reason | EOL of the major it is on after today | |---|---|---|---|---|---|---|---| | outline | 1.9.1 | `postgres:16-alpine` | docs self-host compose: `postgres:18` at `/var/lib/postgresql` (the repo's own compose is dev-only) / v1.10.1 same | PostgreSQL 14+ | **→ 18, moved** | upstream runs 18 | 18: 2030-11-14 | | rallly | 4.11.1 | `postgres:16-alpine` | `postgres:18-alpine` at `/var/lib/postgresql` (v4.11.1 compose) / v4.15.3 same | 18 for fresh installs (rallly-selfhosted README) | **→ 18, moved** | upstream runs 18 at the pinned tag | 2030-11-14 | | sparkyfitness | v0.17.3 | `postgres:15-alpine` | `postgres:18.3-alpine` at `/var/lib/postgresql` (v0.17.3 `docker-compose.prod.yml`) / v1.7.3 same | upstream guide "PostgreSQL Upgrade (15 to 18.3)" | **→ 18, moved** | upstream pairs this very tag with 18 | 2030-11-14 | | zipline | 4.6.1 | `postgres:16-alpine` | `postgres:16` / v4.8.0 `postgres:16` | only "PG 14 supported until 2026-11-12"; nothing on 17/18 | **stay 16** | upstream runs the same major | 16: 2028-11-09 | | adventurelog | v0.13.0 (= newest) | `postgis/postgis:16-3.5-alpine` | `postgis/postgis:16-3.5` / same | not documented (Django 5.2.13, psycopg2) | **stay 16** | upstream runs the same major | 2028-11-09 | | immich | v3.2.2 | `…/postgres:16-vectorchord0.4.3-pgvectors0.2.0` | `…/postgres:14-vectorchord0.4.3-pgvectors0.2.0` / v3.2.4, v3.3.0-rc.0 same | `>= 14, < 20` | **stay 16** | upstream runs an OLDER major (14) than ours | 2028-11-09 | **A2 (the special images), measured from the registries and upstream source — no build, both stay:** - **immich:** the only PostgreSQL 18 images carry VectorChord 0.5.3+ and **no pgvecto.rs**; PostgreSQL 17 carries only pgvecto.rs 0.3.0. So any major move of ours would also swap an extension version → the stop rule would apply. It does not arise: upstream runs 14, so the rule says stay. Recorded in R-463. - **adventurelog (PostGIS):** every candidate tag is PostGIS 3.x; a dump/load is PostGIS's own "hard upgrade" (the dump carries `CREATE EXTENSION postgis`, no `postgis_extensions_upgrade()` needed). Moot: stay 16. ## Part A — per-app outcome (the part-10 method: bench → 9202 → `--write-ladder` → one commit) | app | bench (LXC 9401, harness v4) | box 9202 (controller 0.283.1, the guarded Update) | undo case on 9202 (the load fails: `adminpack`) | memory (anon peak, 10 min) | catalog | |---|---|---|---|---|---| | rallly | converted 21.4 s (dump 0.54 s / 92 kB, load 1.05 s), 31 tables equal, poll read back | CONVERTED 16 → 18 in 7.1 s (2 dbs, 31 tables, 138 rows equal), done 32.9 s, poll read back | undone in 57.5 s, back on 16, poll read back | app 61.6 %, db 2.1 % | `25ffd89` | | outline | converted 26.7 s (dump 0.58 s / 128 kB, load 1.68 s), 42 tables equal, document read back | CONVERTED 16 → 18 in 7.9 s (2 dbs, 42 tables, 312 rows equal), done 41.1 s, document read back | undone in 57.5 s, back on 16, document read back | app 48.9 %, db 5.4 % | `aeb0cd6` | | sparkyfitness | converted 13.3 s (dump 0.69 s / 378 kB, load 3.99 s), 94 tables equal, weight read back | CONVERTED 15 → 18 in 9.8 s (2 dbs, 94 tables, 261 rows equal), done 58.5 s, weight read back | (after a reinstall at 15) undone in 74.9 s, back on 15, weight read back | server 31.3 %, db 1.8 % | `1666572` | No step is `memory_tight`; no limit moved; no per-box cost. Evidence: `bench/apps//bench/`, `box//{prep,move,undo,undo-setup}.txt`, `box//{box-verdict,undo-verdict}-*.json`. Fixtures: catalog `e6f3ec2`. The engine gate printed ALLOWED for all three on push. **Seed routes — all three are the app's own front door; decision 52 was NOT needed and is not recorded:** sparkyfitness (better-auth sign-up + a check-in), outline (`installation.create`, its self-hosted first-run route, + an API key + a document), rallly (sign-up, then the six-digit e-mail code READ from its own `verifications` row in place of a mailbox, verify-email, `polls.make`). Negative control on every read-back. ## Part B — the demo boxes Neither demo box has rallly, outline or sparkyfitness installed (demo-hp 9201: adventurelog, bentopdf, bookstack, calibre-web, docmost, kimai, opengist, paperless-ngx, privatebin, romm; demo-felhom: opengist). Nothing installed for this. Part F's moves (bookstack, kimai) ARE on demo-hp: their conversion-free steps ran through the chain press of Part C — see `C/`. ## Part C, Part D, Part E, Part F — see `C/README.md`, `../catalog-currency-2026-09-30.md`, `E/`, `F/`.