Files
home-network-k3s-impl/platform/image-automation/README.md
jakman85andClaude Opus 4.8 37f4fa6767 feat(updates): install Flux image controllers + cluster-wide write-back engine (R-UPD-02)
Install image-reflector-controller + image-automation-controller as part of the
flux-system install (bootstrap.sh --components-extra) so the image.toolkit CRDs
pre-exist before reconcile — no F-MON1 split needed for the ImageUpdateAutomation CR.
Refine the cluster-wide ImageUpdateAutomation for machine-authored git write-back
(setter strategy, path ./apps) reusing the flux-system GitRepository secretRef, and
document the operator git-write-credential hand-off + SOPS secret template
(gitea-image-automation-credentials). image-automation stays deferred in
platform/kustomization.yaml until that credential is provisioned.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 22:14:39 -05:00

5.1 KiB

platform/image-automation

Flux image-update-automation — hands-off container image tag advances driven by declared policy.

What lives here

  • image-update-automation.yaml — the cluster-wide ImageUpdateAutomation that scans registries per each service's ImagePolicy and commits the resolved stable version back to git (machine-authored, auditable — ADR-0009). No human or AI authors a tag bump (R-UPD-02).
  • per-service/ — the render contract (update block → ImageRepository/ImagePolicy/setter marker) plus a worked example (pihole.example.yaml). Reference only, not deployed — WS-GITOPS emits the equivalent next to each real app's Deployment/HelmRelease under apps/.

Controllers (how the CRDs get here)

The two controllers — image-reflector-controller + image-automation-controller — are installed as part of the flux-system install by bootstrap/flux/bootstrap.sh via flux install --components-extra=image-reflector-controller,image-automation-controller, exactly like the other four Flux controllers. Consequences:

  • The image.toolkit.fluxcd.io CRDs (ImageRepository/ImagePolicy/ImageUpdateAutomation) exist before the first reconcile — just like GitRepository/Kustomization do.
  • So the ImageUpdateAutomation CR here needs no F-MON1 controllers/configs split: F-MON1 bites only when a CR is applied in the same Flux pass that installs its CRD (via a HelmRelease). Here the CRD is a bootstrap artifact, so the CR applies cleanly from the platform Kustomization. (The per-service CRs likewise ride with apps/, which dependsOn platform.)

Scope boundary (contract)

  • This dir: the cluster-wide automation + git write-back settings.
  • apps/<svc>/: per-service ImageRepository / ImagePolicy (semver stable range) + the Deployment setter marker — rendered from each ServiceDefinition's update block (see per-service/README.md).
  • Renovate (/renovate.json): Helm chart / dependency / operator-manifest bumps (opens PRs, auto-merges allowed classes on green CI). Not this controller's job, and deliberately non-overlapping (Renovate's docker/kubernetes image managers are disabled so it never fights Flux over a tag).

Safety rails (acceptance gates — ADR-0009)

Rail Where enforced
Stable-only (R-UPD-01) ImagePolicy semver range + a filterTags pattern that admits only bare X.Y.Z (excludes -rc/latest).
No human/AI authoring (R-UPD-02) image-automation writes the tag at the $imagepolicy setter; the commit is machine-authored (fluxcdbot).
Health-gated auto-rollback (R-UPD-03) HelmRelease upgrade.remediation + rollback (preferred), or Deployment progressDeadlineSeconds + readiness probe.
Pin escape hatch (R-UPD-03) update.pin renders a fixed image with no setter marker and no/suspended ImagePolicy — automation is frozen.
Alert on failure (R-UPD-02 + R-OBS) platform/monitoring/alerts.yaml group platform-health.automated-updates (ImageUpdateAutomationFailing, ImagePolicyStalled, UpdateHealthGateFailed).

Proven by the L3 conformance test conformance/tests/l3-fault-injection/hands-off-update/ (auto-advance stable, bad-update auto-rollback + alert-wired, pin holds).

Operator hand-off: git write credential (required before enabling)

image-automation-controller reuses the flux-system GitRepository's .spec.secretRef for push auth (it has no independent secretRef). The Phase-1 source is anonymous/read-only, so before write-back works the operator must:

  1. Create a Gitea access token (scope write:repository) for the bot and put it in the SOPS secret from the template bootstrap/sops/secret-templates/gitea-image-automation-credentials.yaml.example → platform/image-automation/gitea-image-automation-credentials.sops.yaml (name gitea-image-automation-credentials, keys username/password), then sops --encrypt --in-place.
  2. Add that file to this dir's kustomization.yaml resources:.
  3. Attach it to the source: add spec.secretRef.name: gitea-image-automation-credentials to the flux-system GitRepository via bootstrap/flux/patches/flux-system-source-url.yaml.

Enabling (deferred — like velero/oauth2-proxy)

image-automation is intentionally commented out in platform/kustomization.yaml. Enable it only after the write credential above is provisioned — otherwise the ImageUpdateAutomation reconcile errors on push and, under the unified graph's platform wait, could stall the downstream graph. Uncomment the - image-automation line once the hand-off is done.