Skip to content

Flux image automation for GitOps-driven container updates

Use Flux image-reflector and image-automation controllers to watch container registries and commit new tags into Git automatically.

You have Flux deployed and the team is asking how new container image builds reach production. The mechanical answer — every git push triggers a CI build, the build pushes an image with a SHA tag, somebody updates a YAML in the gitops repo, Flux reconciles — works but is brittle and slow. The clean answer is Flux image automation: image-reflector polls your registry, image-automation commits new tags into Git, the existing reconciliation loop ships them. This article is how we wire that up on real clusters.

How to verify

Both image controllers should be running in flux-system:

kubectl -n flux-system get deploy image-reflector-controller image-automation-controller

ImageRepository, ImagePolicy, and ImageUpdateAutomation resources should be present:

flux get image repository -A
flux get image policy -A
flux get image update -A

A reconcile should observe the registry and report the latest matching tag:

flux reconcile image repository checkout
kubectl -n flux-system describe imagepolicy checkout

What’s happening

There are three CRDs. ImageRepository tells the image-reflector-controller to scan a container registry on an interval, building a list of tags. ImagePolicy selects which tag is “current” — semver range (>=1.0.0 <2.0.0), regex (main-[a-f0-9]+), or alphabetical sort. The latest matching tag is exposed as a status field. ImageUpdateAutomation ties everything together: it watches one or more policies, scans your Git repo for marker comments like # {"$imagepolicy": "flux-system:checkout"}, rewrites the image tag in place, and commits the change.

The trust model is important. The image-automation-controller needs write access to Git — a deploy key with push permission, or a fine-grained PAT. It does not need access to the registry beyond what image-reflector already has. The cluster becomes a CI follower, not a CI source. If you do not want the cluster to commit to your main branch, point automation at a separate branch (flux-image-updates) and gate it through PRs.

The procedure

  1. Install the image controllers. If you bootstrapped without them, re-run with --components-extra:

    flux bootstrap github \
      --owner=$GITHUB_USER \
      --repository=gitops \
      --branch=main \
      --path=clusters/prod \
      --components-extra=image-reflector-controller,image-automation-controller

    Or commit the extra controller manifests into clusters/prod/flux-system/gotk-components.yaml and reconcile.

  2. Register your registry as an ImageRepository. This is per-image, not per-registry:

    apiVersion: image.toolkit.fluxcd.io/v1beta2
    kind: ImageRepository
    metadata:
      name: checkout
      namespace: flux-system
    spec:
      image: registry.example.com/team/checkout
      interval: 5m
      secretRef:
        name: registry-pull

    The registry-pull Secret is a Docker config Secret with read access. Image-reflector pulls the manifest list, not the image layers, so the bandwidth cost is small.

  3. Define the policy that selects the tag. Two common shapes:

    Semver:

    apiVersion: image.toolkit.fluxcd.io/v1beta2
    kind: ImagePolicy
    metadata:
      name: checkout
      namespace: flux-system
    spec:
      imageRepositoryRef: { name: checkout }
      policy:
        semver:
          range: '>=1.0.0 <2.0.0'

    Build-tag with timestamp prefix (e.g. 2026.06.01-abc1234):

    spec:
      imageRepositoryRef: { name: checkout }
      filterTags:
        pattern: '^(?P<timestamp>[0-9]{4}\.[0-9]{2}\.[0-9]{2})-[a-f0-9]+$'
        extract: '$timestamp'
      policy:
        alphabetical:
          order: asc
  4. Mark the manifest where the tag should be substituted. In the Deployment (or HelmRelease) YAML:

    spec:
      template:
        spec:
          containers:
            - name: checkout
              image: registry.example.com/team/checkout:1.2.3 # {"$imagepolicy": "flux-system:checkout"}

    The marker is a comment — Flux preserves it across rewrites.

  5. Create the ImageUpdateAutomation. This is the writer:

    apiVersion: image.toolkit.fluxcd.io/v1beta1
    kind: ImageUpdateAutomation
    metadata:
      name: flux-image-updates
      namespace: flux-system
    spec:
      interval: 1m
      sourceRef:
        kind: GitRepository
        name: flux-system
      git:
        checkout:
          ref: { branch: main }
        commit:
          author:
            name: fluxcdbot
            email: [email protected]
          messageTemplate: |
            Auto-update images
            {{ range .Updated.Images }}
            - {{.}}
            {{ end }}
        push:
          branch: main
      update:
        path: ./clusters/prod
        strategy: Setters

    Now: image-reflector polls every 5 minutes, image-policy picks the highest matching tag, image-automation rewrites the manifest, pushes, source-controller pulls, kustomize-controller applies. End-to-end takes one reconciliation cycle.

  6. Gate via PR if you don’t want auto-merge. Change push.branch to flux-image-updates and instruct image-automation to open a PR through a notification Receiver, or wire a separate bot. Now humans review every image bump.

Operational notes

  • Tag-list cache is per ImageRepository. A registry with thousands of tags slows down the reconcile. Use filterTags.pattern aggressively or split per environment (one ImageRepository per pattern).
  • Semver range with <2.0.0 blocks a major bump. That is by design — manual intervention is required to cross a major. If you want major bumps automated, expand the range; if not, document the boundary.
  • update.strategy: Setters requires marker comments. Without a marker on a line, image-automation leaves it alone. A common bug: someone copies a manifest and forgets to include the comment — the new manifest never auto-updates.
  • Image-automation commits as a bot account. Branch protection rules that require human reviewers will reject the bot’s commits. Either bypass protection for the bot (via a CODEOWNERS rule) or route through a PR.
  • Registry rate limits matter. Docker Hub anonymous pulls are sharply rate limited; image-reflector hits the manifest endpoint, but if the registry returns 429s, the policy goes stale. Use authenticated pulls and a private registry for production-critical images.

Stack Harbor wires Flux image automation for clients who want CI to produce images and the cluster to decide when to ship them — including the registry credentials, the semver vs build-tag conventions, the PR-gated production path, and the alerting on stuck image policies. See how we run that within managed Kubernetes operations.