Flux CD Flux CD

Automate Image Updates with Flux Image Automation

Let Flux detect new container image tags and commit them back to Git.

15 min Intermediate

In most GitOps setups, your CI pipeline builds an image and then commits the new tag to the configuration repository. Flux offers an alternative: two optional controllers watch your container registry, pick the newest tag that matches a rule, and commit the change to Git themselves. The cluster still only changes because of a Git commit, so you keep the audit trail. This guide uses the public podinfo image.

How the pieces fit

Three resources work together:

  • ImageRepository scans a registry for the tags of one image.
  • ImagePolicy chooses one tag from that list, for example the highest semantic version in a range.
  • ImageUpdateAutomation edits the YAML in your Git repository where a marker says to, then commits and pushes.

All three use the image.toolkit.fluxcd.io/v1 API. The older v1beta2 version was removed from the CRDs in Flux 2.9, so migrate any old manifests before you upgrade.

Install the extra controllers

The image controllers are not part of a default install. Bootstrap again, adding them, and allow Flux to push to the repository:

flux bootstrap github \
  --owner=<your-github-user> \
  --repository=fleet-infra \
  --branch=main \
  --path=clusters/dev \
  --personal \
  --components-extra=image-reflector-controller,image-automation-controller \
  --read-write-key

Bootstrap is safe to repeat. It commits the new controller manifests and installs them. --read-write-key makes the deploy key writable so the automation can push. Verify with kubectl get pods -n flux-system.

Scan the registry

apps/podinfo/image-repository.yaml, in the same folder as your deployment:

apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageRepository
metadata:
  name: podinfo
  namespace: flux-system
spec:
  image: ghcr.io/stefanprodan/podinfo
  interval: 5m

For a private registry, add secretRef pointing at a Docker registry secret, created with kubectl create secret docker-registry or flux create secret oci.

Choose a tag with a policy

apiVersion: image.toolkit.fluxcd.io/v1
kind: ImagePolicy
metadata:
  name: podinfo
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: podinfo
  policy:
    semver:
      range: 6.x

semver with range: 6.x selects the highest 6.x version. Other policy types are numerical (highest number) and alphabetical, which suit tags such as build numbers or dates. For rules based on tag names, add filterTags with a regular expression to the policy.

Mark the line to update

Flux updates the exact lines you mark with a comment. In apps/podinfo/deployment.yaml:

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

The marker is namespace:policy-name. By default it replaces the whole image reference. To update only the tag, use {"$imagepolicy": "flux-system:podinfo:tag"}, and for Helm values or Kustomize images entries the same marker works on the line that holds the tag. Anything without a marker is left alone.

Tell Flux how to commit

apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageUpdateAutomation
metadata:
  name: flux-system
  namespace: flux-system
spec:
  interval: 30m
  sourceRef:
    kind: GitRepository
    name: flux-system
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        name: fluxcdbot
        email: fluxcdbot@users.noreply.github.com
      messageTemplate: |
        Automated image update

        {{range .Changed.Changes}}
        - {{.OldValue}} -> {{.NewValue}}
        {{end}}
    push:
      branch: main
  update:
    path: ./apps
    strategy: Setters

The parts that matter:

  • sourceRef is the Git repository to commit to. It must be writable, hence --read-write-key.
  • checkout.ref.branch is the branch it starts from, and push.branch is where the commit goes.
  • update.path limits which folders are scanned for markers.
  • messageTemplate controls the commit message so the history is readable.

Pushing straight to main makes everything fully automatic. While you build trust, push to a separate branch instead (for example push.branch: flux-image-updates), then merge a pull request when you are happy. The change reaches the cluster only after the merge.

Watch it work

Commit and push these files, then check each stage:

flux get image repository
flux get image policy
flux get image update

The repository shows how many tags were scanned, the policy shows the tag it selected, and the update automation shows the last commit it pushed. If the selected tag is newer than the one in your YAML, a commit appears in Git, and Flux then applies it like any other change. To debug, use kubectl describe on each object: a wrong marker, a policy range that matches nothing, or a read-only deploy key are the usual culprits.

Which approach to pick

Use CI-driven commits if one pipeline already owns promotion and you want a single place that decides what ships. Use Flux image automation when you want the cluster configuration repository to follow a registry on its own, for example keeping a dev environment on the latest patch release. The two can coexist.

Popular Guides