Image Automation Overview
The image automation pipeline involves three controllers and four CRDs working together:
- image-reflector-controller scans the container registry and stores available tags in
ImageRepositoryandImagePolicyresources. - An
ImagePolicyselects the "latest" tag matching your policy (semver, alphabetical, or regex filter). - image-automation-controller reads
ImageUpdateAutomationresources, finds files with marker comments, replaces image tags with the policy-selected tag, and commits to Git. - The GitRepository source picks up the new commit, triggers reconciliation, and the updated tag gets deployed.
Install the image controllers: Image automation controllers are not installed by default with
flux bootstrap. Enable them explicitly: flux bootstrap github --components-extra=image-reflector-controller,image-automation-controller ...
ImageRepository
# ImageRepository — tells image-reflector-controller which registry to scan
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: grade-api
namespace: flux-system
spec:
image: ghcr.io/your-org/grade-api # Registry + image (no tag)
interval: 5m # How often to scan for new tags
# For private registries
secretRef:
name: ghcr-credentials # docker-registry type Secret
# Limit tags scanned (reduces API calls)
exclusionList:
- "^.*-dev$" # Exclude dev builds
- "latest" # Exclude mutable 'latest' tag
# Tag filter — only consider tags matching this regex
# (optional, prefer ImagePolicy filter instead)
# Check ImageRepository status and last scanned tags
flux get image repository grade-api
# NAME LAST SCAN TAGS READY MESSAGE
# grade-api 2026-06-06T12:00:00Z 45 True successful scan
# List all available tags (stored in the resource)
kubectl get imagerepository grade-api -n flux-system -o yaml | grep -A 50 scanResult
ImagePolicy
Semver Policy
# Automatically select the highest semver tag matching a constraint
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: grade-api
namespace: flux-system
spec:
imageRepositoryRef:
name: grade-api
policy:
semver:
range: ">=1.0.0 <2.0.0" # Select latest v1.x.x
# After creation, check which tag was selected:
# kubectl get imagepolicy grade-api -n flux-system
# NAME LATESTIMAGE READY
# grade-api ghcr.io/your-org/grade-api:1.5.2 True
Alphabetical Policy
# For date-stamped tags like "20260606-abc1234"
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: grade-api-staging
namespace: flux-system
spec:
imageRepositoryRef:
name: grade-api
filterTags:
pattern: "^[0-9]{8}-[a-f0-9]+" # Match YYYYMMDD- tags
extract: "$timestamp" # Extract for ordering
policy:
alphabetical:
order: asc # Last alphabetically = most recent
Regex Policy
# Pin to a specific channel (e.g., only 'stable' builds)
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: grade-api-stable
namespace: flux-system
spec:
imageRepositoryRef:
name: grade-api
filterTags:
pattern: "^stable-.*"
policy:
alphabetical:
order: asc
ImageUpdateAutomation
# ImageUpdateAutomation — commits updated image tags back to Git
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageUpdateAutomation
metadata:
name: flux-system
namespace: flux-system
spec:
# Git source to commit to
sourceRef:
kind: GitRepository
name: flux-system
# Git author for automated commits
git:
checkout:
ref:
branch: main
commit:
author:
email: fluxcdbot@users.noreply.github.com
name: fluxcdbot
messageTemplate: |
chore(image): update image tags
Flux automated image update:
{{range .Updated.Images}}
- {{.ImageName}}: {{.OldTag}} -> {{.NewTag}}
{{end}}
push:
branch: main # Push directly to main
# Or use a separate branch for PR-based updates:
# branch: flux/image-updates
interval: 1m # How often to check for policy updates
update:
strategy: Setters # Use marker comments in files
path: ./ # Root of the repo to scan for markers
Marker Comments in Manifests
# Add marker comments to YAML files where image tags should be updated.
# The format is: # {"$imagepolicy": "NAMESPACE:NAME"}
# In a Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: grade-api
spec:
template:
spec:
containers:
- name: grade-api
image: ghcr.io/your-org/grade-api:1.5.2 # {"$imagepolicy": "flux-system:grade-api"}
# In a HelmRelease values:
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: grade-api
spec:
values:
image:
repository: ghcr.io/your-org/grade-api
tag: 1.5.2 # {"$imagepolicy": "flux-system:grade-api:tag"}
# The ":tag" suffix extracts just the tag, not the full image reference
# After a new image is pushed, Flux will:
# 1. image-reflector-controller scans ghcr.io/your-org/grade-api
# 2. ImagePolicy selects the new latest tag (e.g., 1.5.3)
# 3. image-automation-controller finds the marker comment
# 4. Updates the YAML file: 1.5.2 → 1.5.3
# 5. Commits and pushes to Git:
# "chore(image): update image tags
# - ghcr.io/your-org/grade-api: 1.5.2 -> 1.5.3"
# 6. source-controller detects the new commit
# 7. kustomize-controller or helm-controller reconciles
# 8. New image deployed to cluster
# Monitor image automation
flux get image all
flux get image update flux-system
Flux Notifications
Slack Provider
# Create a Slack webhook secret
kubectl create secret generic slack-url \
--namespace=flux-system \
--from-literal=address=https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK
# Provider — defines where to send notifications
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
name: slack-deployments
namespace: flux-system
spec:
type: slack
channel: "#k8s-deployments" # Slack channel name
secretRef:
name: slack-url # Secret containing webhook URL
username: "Flux" # Bot display name
Alert Configuration
# Alert — defines what events trigger notifications
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
name: on-call-slack
namespace: flux-system
spec:
providerRef:
name: slack-deployments
eventSeverity: info # info | error (error = only send on failures)
eventSources:
# Watch all Kustomizations in flux-system
- kind: Kustomization
namespace: flux-system
name: "*" # Wildcard — watch all
# Watch specific HelmRelease
- kind: HelmRelease
namespace: flux-system
name: grade-api
# Watch GitRepository for source pull failures
- kind: GitRepository
namespace: flux-system
name: "*"
exclusionList:
- ".*no new images.*" # Don't alert for image scan no-ops
- ".*ReconcileSucceeded.*" # Only want failures on this alert
# Separate alert for failures only (pinging a different channel)
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
name: failures-slack
namespace: flux-system
spec:
providerRef:
name: slack-failures # Different provider pointing to #k8s-alerts
eventSeverity: error # Only error events
eventSources:
- kind: Kustomization
namespace: flux-system
name: "*"
- kind: HelmRelease
namespace: flux-system
name: "*"
PagerDuty & Other Channels
# PagerDuty provider
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
name: pagerduty
namespace: flux-system
spec:
type: pagerduty
secretRef:
name: pagerduty-token # Secret with key: token=
---
# Microsoft Teams provider
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
name: teams
namespace: flux-system
spec:
type: msteams
secretRef:
name: teams-webhook-url # Secret with key: address=
---
# GitHub commit status (marks commits with deployment status)
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
name: github-status
namespace: flux-system
spec:
type: github
address: https://github.com/your-org/grade-api-gitops
secretRef:
name: github-token # Secret with key: token=
# Test notification delivery
flux create alert-provider slack-test \
--type=slack \
--channel=#k8s-test \
--secret-ref=slack-url
# Trigger a test event
flux reconcile kustomization flux-system
# You should see a Slack message appear
# Check notification provider health
flux get alert-providers
flux describe alert-provider slack-deployments
Exercises
Exercise 1 — Image Scanning: Create an ImageRepository for a public image (e.g.,
ghcr.io/stefanprodan/podinfo). Create an ImagePolicy with semver range >=6.0.0. Check the selected tag with flux get image policy.
Exercise 2 — Full Automation Loop: Add a marker comment to a Deployment manifest in your Git repo. Create an ImageUpdateAutomation. Push a new image tag that matches your policy. Watch Flux detect the new tag, update Git, and redeploy.
Exercise 3 — Slack Alerts: Set up a Slack webhook (use Slack's Incoming Webhooks app). Create a Provider and an Alert watching all Kustomizations. Force a reconciliation failure (invalid path) — verify the Slack alert fires. Fix it — verify the recovery alert fires.
Key Takeaways
Key Takeaways:
- Image automation closes the GitOps loop: code push → CI builds image → Flux updates Git tag → cluster deploys new image
- Three CRDs:
ImageRepository(scans registry) →ImagePolicy(selects tag) →ImageUpdateAutomation(commits to Git) - Marker comments (
# {"$imagepolicy": "ns:name"}) tell Flux exactly which field to update in which file - Notifications use three CRDs:
Provider(where to send) +Alert(what triggers it) - Separate alert severities:
infofor #deployments channel,errorfor #on-call channel - Image automation controllers must be explicitly enabled during bootstrap with
--components-extra