Skip to content

ADR-015: Flux Image Automation

Status

Accepted (implemented — ImageRepository, ImagePolicy, and ImageUpdateAutomation active for homekube-docs)

Date

2026-03-21

Context

The current GitOps loop is manual for container image updates: when a new image tag is available, the engineer must edit a deployment manifest, commit, and push. Flux's Image Automation components close this loop by:

  1. Polling a container registry for new tags (image-reflector-controller)
  2. Matching tags against a policy (semver range, regex, etc.) (image-reflector-controller)
  3. Committing an updated image tag back to Git (image-automation-controller)
  4. Flux then reconciles the updated manifest into the cluster in the normal way

The standard Flux bootstrap installed four controllers: source-controller, kustomize-controller, helm-controller, and notification-controller. The two image controllers are separate optional components and were not included in the initial bootstrap.

Decision

Install the image controllers by re-running flux bootstrap with --components-extra:

flux bootstrap github \
  --owner=MarcSpeckmann \
  --repository=HomeKube \
  --branch=main \
  --path=clusters/local \
  --personal \
  --components-extra=image-reflector-controller,image-automation-controller

This updates clusters/local/flux-system/gotk-components.yaml (the DO NOT EDIT bootstrap file) to add the two controller Deployments.

Scaffold the image-automation/ directory with an empty Kustomization so the Flux Kustomization object (clusters/local/image-automation.yaml) reconciles cleanly now and is ready for resources when the first workload is deployed.

Defer ImageRepository, ImagePolicy, and ImageUpdateAutomation resources until a workload with a tracked image is deployed.

Branch strategy for future automation

When image automation resources are added, the ImageUpdateAutomation will push tag updates to a dedicated branch:

spec:
  git:
    push:
      branch: flux/image-updates
    commit:
      author:
        name: Flux Image Automation
        email: flux@homekube.local

This means automated tag bumps never land directly on main — they arrive as a branch that can be reviewed and merged. Appropriate for a GitOps repo where main is the production source of truth.

Marker comment format (for future use)

Deployment manifests must carry a policy marker comment for the automation to update them:

containers:
  - name: podinfo
    image: ghcr.io/stefanprodan/podinfo:5.0.3 # {"$imagepolicy": "flux-system:podinfo"}

The ImageUpdateAutomation scans files in the configured path for these markers and replaces the tag when a new policy-matched tag is found.

Alternatives Considered

Option Reason Not Chosen
Manual image tag updates Error-prone and breaks the GitOps automation principle
GitHub Actions image update workflow Works but requires a PAT with repo write scope and is not driven by registry events; also duplicates what Flux already supports natively
Renovate Bot Good for dependency PRs but not registry-event-driven; would complement but not replace Flux Image Automation
Skip image automation entirely Defeats the purpose of a full CNCF learning sandbox

Consequences

Positive

  • Closes the GitOps loop — no manual image tag editing
  • flux/image-updates branch gives a review gate before changes land on main
  • Image controllers are lightweight and add minimal resource overhead
  • Pattern is directly transferable to production Flux setups

Negative

  • Two additional controllers running in flux-system namespace (~50 MB memory combined)
  • gotk-components.yaml must be regenerated via flux bootstrap (cannot be hand-edited)
  • The flux/image-updates branch must be merged manually — automation is not fully hands-off
  • Requires a container registry accessible from the cluster (ghcr.io works from minikube)

Future Resources (add to image-automation/ when deploying an app)

# image-automation/<app>-imagerepository.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
  name: <app>
  namespace: flux-system
spec:
  image: ghcr.io/<owner>/<app>
  interval: 5m

---
# image-automation/<app>-imagepolicy.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: <app>
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: <app>
  policy:
    semver:
      range: ">=1.0.0"

---
# image-automation/automation.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageUpdateAutomation
metadata:
  name: homekube
  namespace: flux-system
spec:
  interval: 5m
  sourceRef:
    kind: GitRepository
    name: flux-system
    namespace: flux-system
  git:
    push:
      branch: flux/image-updates
    commit:
      author:
        name: Flux Image Automation
        email: flux@homekube.local
      messageTemplate: "chore: update {{range .Updated.Images}}{{println .}}{{end}}"
  update:
    strategy: Setters
    path: ./apps