Skip to content

Image updates — pinning & update notifications

How container images are versioned in this repo and how we get told when updates ship.

Convention: pin explicitly, watch the repo

Every service in stacks/*/ is pinned to an explicit version tag — no latest, stable, production, or bare-variant tags (rocm) on running services. Each watchable service also carries Diun watch_repo labels so Diun alerts on new version releases (not just a digest change on the tag you're already running):

    labels:
      diun.watch_repo: "true"
      diun.include_tags: '<regex for this image's tag scheme>'
      diun.sort_tags: "semver"
      diun.max_tags: "3"

include_tags regex per scheme (Go RE2, single-quoted in YAML so backslashes survive):

Scheme Example image include_tags
vX.Y.Z adguard, traefik, homarr, postmoogle ^v\d+\.\d+\.\d+$
X.Y.Z zigbee2mqtt, uptime-kuma, forgejo, frigate ^\d+\.\d+\.\d+$
CalVer YYYY.M.P home-assistant, esphome ^\d{4}\.\d+\.\d+$
X.Y.Z-<variant> mosquitto (-alpine), peertube (-trixie), ollama (-rocm) ^\d+\.\d+\.\d+-alpine$ etc.
Base OS major postgres/redis (-alpine) ^\d+-alpine$
Base OS minor alpine ^\d+\.\d+$

Left unwatched on purpose: digest-pinned images (@sha256:… — otbr, matter-server, immich pg/redis) have no tag to walk; the locally-built beszel-hub-patched isn't in a registry (rebuild on bumps — see its compose comment); Immich is a coupled stack on a version-locked pgvecto.rs DB image and is bumped as a set per upstream release notes, not piecemeal.

Registry sources: keeping off Docker Hub

watch_repo makes Diun list a repository's full tag set each scan to find the newest, then digest-check the top max_tags (3). Docker Hub's anonymous pull limit (100/6h) is easily exhausted by the tag-heavy Official Images (postgres, python have thousands of tags to page), which then return HTTP 429.

Rather than authenticate to Docker Hub (which needs an account + brings the tracking that comes with one), images are sourced from mirrors that need no account and have no restrictive anonymous cap:

  • Docker Official Imagespublic.ecr.aws/docker/library/* (AWS ECR Public mirror): postgres, redis, python, alpine, traefik, eclipse-mosquitto.
  • Third-party images that also publish to ghcrghcr.io/*: crazy-max/diun, louislam/uptime-kuma, tecnativa/docker-socket-proxy.
  • Still on Docker Hub (no public ghcr mirror; 403): adguard/adguardhome, henrygd/beszel-agent, livekit/livekit-server, rhasspy/wyoming-{piper,whisper}, mrcreativ3001/moonlight-web-stream, ollama/ollama — few tags each, so they stay under the anon limit.

After repointing, a full Diun scan reports 0 × 429. Trade-offs: a mirror can lag Docker Hub by hours–days on a brand-new release (a "new version" alert may arrive slightly later), and you must verify the exact pinned tag resolves on the mirror before repointing (they occasionally lag). The include_tags regexes are unchanged — mirrors carry the same tag names.

diun.max_tags: 3 does cap the digest checks to the newest 3/repo (a full scan is ~115 checks, not hundreds — diun image inspect prints the full tag listing, not the watched set). What tripped the limit was the tag listing itself, which max_tags can't shrink — hence mirrors, not auth.

Renovate (not deployed)

stacks/renovate/ exists but has never run — pinned-version bumps are handled by the Diun watch_repo alerts above instead. To revive Renovate it needs: a stacks/renovate/.env with a Forgejo token, this repo actually pushed to Forgejo (currently no git remote; Forgejo has 0 repos), and either re-enabling Forgejo issues (disabled per ADR-0003) or the PR-only config already set in renovate.json (dependencyDashboard: false).

Postgres major upgrades (16 → 18)

postgres:18+ crash-loops if the volume is mounted at /var/lib/postgresql/data directly — it now requires a subdirectory. Set PGDATA=/var/lib/postgresql/data/pgdata (matches ha-postgres). A major bump is not a tag change; migrate via dump/restore:

docker exec <pg-old> pg_dumpall -U <superuser> > backup.sql     # 1. dump (while old is running)
# 2. edit compose: image -> :18-alpine, add PGDATA subdir, point volume at a NEW name (keep old for rollback)
docker compose -f <stack> up -d --wait postgres                 # 3. fresh 18 initialises
docker exec -i <pg-new> psql -U <superuser> < backup.sql        # 4. restore

Done 2026-07-19 for byos_postgres and peertube_postgres; PeerTube app also went 7.3.0 → 8.2.2 (bookworm → trixie). Dumps + row-count baselines kept in /home/skill-lava/pg-backups/.

Gotchas

  • Registry tag lookups lie. Paginated registry tag lists can miss the newest line (frigate's stable = 0.17.2 while a naive scan returned 0.16.1; same for mosquitto, open-webui). Always check the running version before pinning; pin to current when unsure.
  • Frigate config schema changes between minors. 0.16 dropped the old record: continuous/motion: {days} retention format and changed classification: custom. Pinned at 0.17.2 (current). Moving the config forward is a manual migration — a downgrade to 0.16 crash-loops on the 0.17 config.