30.5 Image Automation: Updating Image Tags in Git
Right, so you’ve got Flux deployed and your manifests are happily syncing from Git. Fantastic. But let’s be honest: you’re not really doing GitOps until you’ve automated the most common, tedious, and error-prone task of them all—updating a flipping image tag.
You know the drill. A new version of your app (v1.2.3) gets built, pushed to your registry, and now you, the brilliant human, have to:
git clonethe repo.- Manually find and edit
deployment.yaml(or worse, a Kustomize patch). git commit -m "bump image to v1.2.3 because I am a glorified search-and-replace tool".git push.- Wait for Flux to sync the change.
- Hope you didn’t typo
v.1.2.3and break everything.
This is absurd. We have robots for this. Flux’s Image Automation controllers are those robots. Their job is to watch your container registry, notice new tags, and—this is the key part—write a new commit back to your Git repository with the updated tag. The automation loop is closed. You get a pull request or a direct commit (your choice, you maniac) and Flux applies the change itself. It’s beautiful.
How the Magic Trick Works: Observers and Reflectors
The system has two main parts, and you need both. Don’t skip one and then email me asking why it’s broken. I will know.
First, you need an ImageRepository (the observer). This tells Flux what to watch. You point it at a container registry and a repository name. It will sit there, politely polling the registry (you can configure the interval, because you’re polite too), and compiling a list of all available tags.
# This goes in your cluster, not your Git repo.
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: my-app-repo
namespace: flux-system
spec:
image: ghcr.io/your-org/your-awesome-app
interval: 5m # Check every 5 minutes. Don't set this to 5s, you'll annoy the registry.
Second, you need an ImageUpdateAutomation (the reflector). This is the part that actually does the scary thing: writing to Git. It’s constantly watching all the ImageRepository resources. When it sees a new tag that matches a filter you’ve defined, it springs into action. It will:
- Clone your Git repo.
- Run a
sed-like find-and-replace across the specified paths. - Commit the change back to the branch.
- Push it.
You must tell it how to commit and where to push. This is where you configure your Git credentials (usually with a deploy key).
# Also applied to the cluster.
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageUpdateAutomation
metadata:
name: update-app-images
namespace: flux-system
spec:
interval: 1m # How often to check for new images and run the update
sourceRef:
kind: GitRepository
name: flux-system # The name of your main GitRepository object
git:
commit:
messageTemplate: |-
chore: automated image update
{{ range .Updated.Images }}
- {{ .Name }} to {{ .NewTag }}
{{ end }}
# This is crucial for authentication
push:
branch: main
update:
strategy: Setters # The other option is 'Letters', ignore it for now.
# This is the money part: where to look and what to change.
patchStrategicMerge:
files:
- ./apps/my-app/overlays/production/deployment.yaml
target:
kind: Deployment
name: my-app-deployment
container: my-app-container # The specific container in the pod spec
The “What Tag Should I Even Use?” Problem
You’re not just interested in any new tag. If your team pushes main-123abc on every commit, you probably don’t want to automatically deploy every single one to production. The ImageRepository resource uses a tag policy to filter this.
The most useful one is SemVer. It does exactly what you think: finds the highest version that matches a pattern.
# Inside your ImageRepository spec
spec:
image: ghcr.io/your-org/your-awesome-app
interval: 5m
# Tag policy section
tagPolicy:
semver:
range: 1.0.x # Will pick the latest v1.0.x patch version. Stable.
# range: '>=1.0.0 <2.0.0' # More complex example for all non-breaking changes.
For those of you living on the edge with latest or commit SHAs, there’s a Regex policy. Use this power wisely.
tagPolicy:
regex: '^main-[a-f0-9]+-(?P<ts>[0-9]+)' # Extract a timestamp from the tag
The Crucial Git Security Dance
This is the part where everyone gets stuck. The ImageUpdateAutomation needs write access to your Git repo. No write access, no automated commits. The best practice is to use a deploy key.
Generate an SSH key pair:
ssh-keygen -t ed25519 -C "flux-image-automation" -f flux-image-automation.Add the public key (
flux-image-automation.pub) as a Deploy Key in your GitHub/GitLab repo. You must grant it write access. The checkbox is right there. Don’t forget to check it.Create a Kubernetes Secret with the private key.
kubectl create secret generic flux-image-automation-ssh \ --namespace=flux-system \ --from-file=identity=./flux-image-automationTell your
ImageUpdateAutomationto use it:spec: git: checkout: ref: branch: main commit: # ... message template ... push: branch: main # This references the secret you just created secretRef: name: flux-image-automation-ssh
Best Practices and “Oh, That’s Why”
- Use Pull Requests, You Maniac: Direct commits to main are fine for a demo. For anything real, configure your
ImageUpdateAutomationto push to a branch (automated/image-updates) and open a Pull Request. This gives you a chance to run CI checks before the change is merged and synced. This is configured in thegit.push.branchfield. - Be Specific with Your Container Names: In your
patchStrategicMergesection, always specify thecontainername. If your Pod has multiple containers, and you don’t specify, Flux will just update the first one, which is rarely what you want. - The Mighty Message Template: That
messageTemplateisn’t just for show. It’s your audit log. The{{ .Updated.Images }}block will list every image that was updated in that commit, which is invaluable when debugging. - It’s Just YAML: Remember, Flux isn’t parsing your YAML with black magic. It’s essentially doing a structured find-and-replace. If your deployment manifest is built in a weird, non-standard way (e.g., the tag is in a ConfigMap that gets referenced), this strategy won’t work. Keep it simple.
Get this right, and you’ll never manually bump a tag again. You’ll just sit back and watch the robots argue with each other in Git commits. It’s the future.