Files
app-catalog-felhom.eu/REPORT.md
T

5.2 KiB
Raw Blame History

REPORT — image pinning: :latest eliminated from the catalog (2026-07-12)

Class: implementation (template change + sweep gate; push = deploy). Baseline: main @ 2ebe082 → (this commit). Scope: 5 compose image lines + new gate script

  • CLAUDE.md rule. No healthcheck/env/format touch-ups; no controller change; no redeploys triggered.

Why

The controller's recovery-unit ImagePins (felhom-controller internal/backup/recovery_unit.go) pins the compose tag: restoring a :latest-pinned app re-pulls whatever :latest points to at restore time — potentially schema-incompatible with the data being restored. Load-bearing rule: a deployed app pins to the digest it is currently running (the pin never causes a version jump); undeployed apps pin to the latest verified upstream stable.

The five pins (old → new, with evidence)

App New pin Evidence (all gathered 2026-07-12)
calibre-web (DEPLOYED, demo 9201) crocodilestick/calibre-web-automated:v4.0.6 Running RepoDigest on 9201: sha256:c31a738b6d5e…; Docker Hub tag v4.0.6 digest: sha256:c31a738b6d5e…exact match (Scenario A: pin == running image; a newer tag would have violated the no-silent-upgrade guard)
bentopdf (undeployed) ghcr.io/alam00000/bentopdf:v2.8.6 GitHub latest release v2.8.6 (2026-06-28); ghcr manifest digest sha256:eaeea1e4… == latest
papra (undeployed) ghcr.io/papra-hq/papra:26.6.1-rootless ghcr latest digest sha256:a7a42e22… == 26.6.1-rootless; the -root variant differs (c5757540…) — the rootless variant is what :latest was shipping, so behavior (permission semantics) is preserved exactly
recipe-importer (own image, undeployed) gitea.dooplex.hu/admin/recipe-importer:v0.9.11 Its repo CHANGELOG declares v0.9.11 (2026-02-26); the registry already had v0.9.11 (build.sh pushes :VERSION+:latest together), digest sha256:f3cb617c… == latestScenario C's retag was unnecessary; no push performed
termix (undeployed) ghcr.io/lukegus/termix:2.5.0 ghcr digests: latest == 2.5.0 == release-2.5.0 (sha256:4d337131…); bare CalVer tag chosen

All five docker manifest inspect <ref> checks ran on the build server (180) with per-command exit codes checked — 5× OK, zero pipe-swallowed.

templates.json: contains no image strings (legacy Portainer-era metadata) — untouched, per §4.3.

Sweep + red-proofs (§10 / Scenario D)

  • New rerunnable gate: scripts/check-image-pins.py — scans every templates/*/docker-compose.yml image: line; fails on floating tags (latest,dev,nightly,edge,main,master) AND on untagged refs (implicit :latest); @sha256: counts as pinned; registry-port refs handled.
  • Final run: image-pin gate OK — 53 templates, 0 unpinned images, exit 0.
  • Red-proof 1 (floating tag): reverted termix to :latest → exit 1 with templates/termix/docker-compose.yml:11 … [floating tag :latest] → restored → exit 0. ✓
  • Red-proof 2 (bare shape): stripped termix's tag entirely → exit 1 with [NO TAG (implicit :latest)] → restored → exit 0. ✓ (Both §7-D shapes proven.)

Fleet-deployment verification (Scenario B)

  • demo 9201: docker ps — bentopdf/papra/termix/recipe-importer not deployed; calibre-web running from :latest (Up 4 hours at check time).
  • felhotest (router.abonet.hu:33022): unreachable (connection refused).
  • Peti's box: offline (dead fan, no ETA).
  • Operator asked and approved proceeding: every pin is digest-identical to current :latest, so even a hidden deployment elsewhere sees zero change vs. the status quo on its next redeploy.

Git-sync no-op verification on 9201

  • Pushed 71828a8; sync triggered via docker exec felhom-controller curl -s -X POST http://127.0.0.1:8080/api/sync → HTTP 200. (Gotcha confirmed: guest-netns 127.0.0.1:8080 is NOT reachable — the API must be curled from inside the controller container; the public-URL POST proxy gotcha also applies.)
  • Synced file on 9201: /opt/docker/stacks/calibre-web/docker-compose.yml:21 now reads image: crocodilestick/calibre-web-automated:v4.0.6. ✓
  • calibre-web container after sync: crocodilestick/calibre-web-automated:latest | Up 5 hours (healthy)uptime unchanged, no restart; the running image's digest equals the v4.0.6 pin, so the next redeploy pulls the byte-identical image. ✓

Observations (not acted on)

  • When Peti's box returns: check whether bentopdf/papra/termix/recipe-importer are deployed there; if any is running an OLDER digest than the pin, decide upgrade vs. per-node pin then. felhotest should get the same check when reachable again.
  • papra upstream publishes only -root/-rootless variant tags — any future manual upgrade must keep the -rootless suffix or permissions semantics change.
  • bentopdf's ghcr tags/list API returns a stale/partial page (newest listed was 1.15.3 while v2.8.6 exists and serves) — tag existence must be checked via manifest HEAD/inspect, not tags/list.
  • recipe-importer's registry :latest remains published (allowed; only templates must not use it).