Back to Distributed Systems & Kubernetes Series

Flux Track Part 4: Image Automation & Alerts

June 6, 2026 Wasil Zafar 38 min read

The final piece of the Flux GitOps loop: when a new container image is published, Flux detects it, selects the right tag based on your policy (semver, alphabetical, regex), writes the new tag back to Git, and triggers a reconciliation. Combine with alerts so your team knows when deployments succeed or fail.

Table of Contents

  1. Image Automation Overview
  2. ImageRepository
  3. ImagePolicy
  4. ImageUpdateAutomation
  5. Marker Comments in Manifests
  6. Flux Notifications
  7. Exercises
  8. Key Takeaways

Image Automation Overview

The image automation pipeline involves three controllers and four CRDs working together:

  1. image-reflector-controller scans the container registry and stores available tags in ImageRepository and ImagePolicy resources.
  2. An ImagePolicy selects the "latest" tag matching your policy (semver, alphabetical, or regex filter).
  3. image-automation-controller reads ImageUpdateAutomation resources, finds files with marker comments, replaces image tags with the policy-selected tag, and commits to Git.
  4. 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: info for #deployments channel, error for #on-call channel
  • Image automation controllers must be explicitly enabled during bootstrap with --components-extra