--- date: 2026-10-30 branch: feat/pin-pihole-jackett-plex title: Pin pihole, jackett and plex to the digests already running, and make Renovate able to follow them --- ## What changed - **`catalog/{pihole,jackett,plex}.yaml` `source.image`** pinned to the tag whose digest was already running, so the *image* does not change: - `pihole/pihole` (no tag) -> `pihole/pihole:2026.10.0` - `lscr.io/linuxserver/jackett:latest` -> `lscr.io/linuxserver/jackett:v0.24.2865-ls7` - `plexinc/pms-docker:latest` -> `plexinc/pms-docker:1.43.4.10903-e5521bd8c` - **`apps/wave-0/{pihole,jackett,plex}.yaml`** re-rendered (`render.py --all catalog -o apps --wave-layout --no-image-automation`, in the toolbox). The diff is exactly the three app-container `image:` lines; nothing else moved. - **`catalog/jackett.yaml`** `update.range` comment: "stay on the Jackett 0.22.x stable line" was false (0.24 runs, and `>=0.22.0` has no ceiling). Range unchanged; comment corrected. - **`renovate.json5`**, four packageRules plus a comment block: - jackett `versioning: regex:^v(major).(minor).(patch)-ls(build)$` (Renovate's linuxserver example). - jackett **`overridePackageName: ghcr.io/linuxserver/jackett`**: not in the brief, and needed. Renovate's docker datasource reads at most `dockerMaxPages` (20, a global-only option) tag pages from any host except a short list that includes ghcr.io (1000 pages). lscr.io is not on that list. Jackett has **32,093** tags, oldest first, ~1000 per page; the running tag is #31,980. Before this rule the dry run extracted jackett at `v0.24.2865-ls7` and **proposed nothing**, with no warning. The override changes only the lookup; the manifests keep `lscr.io/...`. Every jackett rule matches on `depName`, because the override rewrites `packageName`. - jackett `schedule: after 3am and before 6am on sunday`, i.e. weekly. Measured: 13 ls builds in 12 days (ls7 built 2026-10-17, ls20 built 2026-10-29). Each is a `patch` and auto-merges, so on the daily window it would mean a PR, a gate run and a jackett restart (Recreate) every day. Weekly picks the newest tag, so it skips nothing. `catalog/jackett.yaml update.cadence` still says daily. That field fed the retired Flux image-automation, and the rule's description says the rule is now the cadence that takes effect. - plex `versioning: regex:^(major).(minor).(patch).(build)-[0-9a-f]+$`. - pihole: **no rule**, as the operator decided. CalVer parses under default docker versioning. Patches and minors auto-merge, and a year rollover is a major (dashboard approval). This is written in a comment. - Corrected the "WHAT RENOVATE STILL CANNOT FIX" comment. It listed these three as floating; they publish immutable version tags. - No `allowedVersions` mirror was added. None of the three catalog ranges has a ceiling (`>=2024.0.0`, `>=0.22.0`, `>=1.40.0`), so #86's mirror has nothing to encode for them. - **Runbooks:** `image-policy-stalled.md` no longer says jackett and pms-docker are `latest`-only. In `plex-quicksync.md`, "the floating-image caveat" became "the image-version caveat", because the deployment no longer runs `:latest` with an implicit `Always`, and the symptom is now "after a pms-docker bump". `plex-claim.md`'s two "running `:latest`" mentions are now in the past tense. ## What I verified - **Live digests** (read-only `kubectl --context k3s-prod`, pod `.status.containerStatuses[].imageID`): pihole-0 and pihole-1 `sha256:d34134716fc3…`, jackett `sha256:4fcf3cc22dd3…`, plex `sha256:e0ab27395614…`. - **Each chosen tag resolves to the same digest** (anonymous registry HEAD, OCI index + manifest-list Accept): `pihole/pihole:2026.10.0` = `sha256:d34134716fc3…`; `ghcr.io/linuxserver/jackett:v0.24.2865-ls7` = `sha256:4fcf3cc22dd3…`; `plexinc/pms-docker:1.43.4.10903-e5521bd8c` = `sha256:e0ab27395614…`. (For contrast, `pms-docker:latest` is now `sha256:6d778d41…` and `jackett:latest` `sha256:ee6ae915…`. The pods would have drifted on their next restart.) - `renovate-config-validator` (ghcr.io/renovatebot/renovate:44.167.0) reports "Config validated successfully". The only warning is the existing `docker` block migration. - **Renovate dry run** (`RENOVATE_PLATFORM=local`, `--dry-run=full`, 44.167.0, anonymous, includePaths limited to the six files). It extracted all three from both `catalog/` (custom regex) and `apps/wave-0/` (kubernetes) under the same depName, so each lands in one branch: - plex `1.43.4.10903-e5521bd8c` -> `1.43.5.11079-b3b13e506` (patch, `renovate/plexinc-pms-docker-1.x`) - jackett `v0.24.2865-ls7` -> `v0.24.2961-ls20` (patch, `renovate/lscr.io-linuxserver-jackett-0.x`). This needs the ghcr override; without it there was no proposal. - pihole `2026.10.0` -> none. That is correct, because 2026.10.0 is the newest numbered tag on Docker Hub. - **Automerge, schedule and dashboard flags** come from Renovate's own `applyPackageRules` (datasource-merge, then update-type-merge, the same order as `flatten.js`), run on the resolved config. The local platform does not run the branch worker: - plex patch: automerge **true**, daily window. - jackett patch: automerge **true**, schedule **sunday** window. - pihole (synthetic updates) `2026.10.1` patch and `2026.11.0` minor: automerge **true**. `2027.01.0` major: automerge **false**, `dependencyDashboardApproval` **true**, +needs-review/breaking. - **Versioning filters** (Renovate's versioning module): the jackett regex rejects `latest`, bare `0.24.2961`, `amd64-…` and `development-…`. The plex regex rejects `latest`, `public`, `beta`, `plexpass` and `…-amd64`/`…-arm64`. Docker versioning ranks `2026.09.0 < 2026.10.0`. - Touched paths (`catalog/`, `apps/`, `renovate.json5`, `runbooks/`, `decisions/`) are not in `infra/gitea/approval-policy.json` `sensitive_paths`. ## What I did NOT verify - **That the merge is restart-free. It is not.** The pod-template `image` string changes, so Flux rolls all three: plex and jackett are `Recreate`, so each has a brief outage, and pihole is a 2-replica StatefulSet that rolls one pod at a time. The digest is identical, so the image's behaviour should not change, but the kubelet resolves the new tag name and may pull it. The rollout itself was not observed. - The implicit `imagePullPolicy` moves from `Always` (`:latest` or no tag) to `IfNotPresent`. This is intended, and was not observed live. - A real (non-dry) Renovate run against Gitea, and whether the Sunday window actually opens a jackett PR. The schedule was only evaluated, not exercised. - Other `lscr.io/linuxserver/*` images (radarr, sonarr, qbittorrent) may hit the same 20-page truncation once their tag counts grow. Not measured here, and not changed. - Plex not self-updating on a version tag was checked only against the running pod (read-only `kubectl exec cat`). `/etc/cont-init.d/50-plex-update` does `exit 0` when `version` from `/version.txt` equals the installed `plexmediaserver` package. Both are `1.43.4.10903-e5521bd8c`. I did not boot a pod on a *new* tag to watch that short-circuit.