Flux GitOps with Kubernetes: Part 3 - Image Automation
Learn how to automate container image updates in Kubernetes using Flux Image Automation Controllers. This guide covers ImageRepository for scanning registries, ImagePolicy for selecting versions, and ImageUpdateAutomation for automatic Git commits.
Introduction
In Part 2, we explored advanced Kustomization features for managing multi-environment deployments with Flux. Now, we’ll tackle one of the most powerful automation features in Flux: Image Automation.
Image Automation allows Flux to automatically detect new container image versions in your registries and update your Git repository with the latest tags. This creates a fully automated deployment pipeline where new image builds trigger automatic updates without manual intervention.
What You’ll Learn
- How Image Automation Controllers work (Image Reflector and Image Automation)
- Scanning container registries with
ImageRepository - Creating version selection policies with
ImagePolicy - Automating Git commits with
ImageUpdateAutomation - Using image policy markers in manifests
- Authentication for different container registries (Docker Hub, GHCR, ECR, GCR, ACR)
- Best practices for production image automation
Prerequisites
- Completed Part 1 and Part 2
- Working Kubernetes cluster (kind, k3s, or production)
- Flux CLI installed (
flux --version) - Git repository for your manifests
- Access to a container registry
All examples in this post are available in the flux-demo-website repository.
Understanding Flux Image Automation
Flux Image Automation consists of two controllers that work together:
1. Image Reflector Controller
Scans container image registries at regular intervals and stores the discovered tags in an internal database. It provides:
- ImageRepository: Defines which container registry to scan
- ImagePolicy: Selects the “latest” image based on policy rules (semver, regex, alphabetical, numerical)
2. Image Automation Controller
Updates Kubernetes manifests in Git repositories with new image tags selected by ImagePolicy objects:
- ImageUpdateAutomation: Defines how and when to update Git repositories
- Commits changes automatically with customizable messages
- Supports multiple Git providers (GitHub, GitLab, Bitbucket, Azure DevOps, Gitea, Forgejo, Gerrit)
How It Works
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
┌────────────────┐
│ Container │
│ Registry │
│ (Docker Hub, │
│ GHCR, ECR, │
│ GCR, ACR) │
└────────┬───────┘
│
│ 1. Scan for tags
↓
┌────────────────────┐
│ ImageRepository │◄─── interval: 5m
│ (image-reflector) │
└────────┬───────────┘
│
│ 2. Store tags
↓
┌────────────────────┐
│ ImagePolicy │◄─── policy: semver
│ (image-reflector) │ range: 1.x.x
└────────┬───────────┘
│
│ 3. Select latest
↓
┌────────────────────────┐
│ ImageUpdateAutomation │◄─── interval: 30m
│ (image-automation) │
└────────┬───────────────┘
│
│ 4. Update manifests
│ 5. Commit & push
↓
┌────────────────┐
│ Git Repository │
│ (main branch) │
└────────┬───────┘
│
│ 6. Flux reconciles
↓
┌────────────────┐
│ Kubernetes │
│ Cluster │
└────────────────┘
Installing Image Automation Controllers
First, verify if the controllers are already installed:
1
flux check
If not installed, bootstrap Flux with image automation:
1
2
3
4
5
6
flux bootstrap github \
--owner=<your-github-username> \
--repository=<your-repo-name> \
--path=clusters/my-cluster \
--personal \
--components-extra=image-reflector-controller,image-automation-controller
Or install them separately on an existing Flux installation:
1
2
flux install \
--components-extra=image-reflector-controller,image-automation-controller
Verify installation:
1
2
3
kubectl get pods -n flux-system | grep image
# image-automation-controller-xxx 1/1 Running
# image-reflector-controller-xxx 1/1 Running
ImageRepository: Scanning Container Registries
The ImageRepository resource tells Flux which container registry to scan and how often.
Basic ImageRepository (Public Registry)
For public registries like Docker Hub or GHCR without authentication:
1
2
3
4
5
6
7
8
9
10
# clusters/my-cluster/imagerepositories/flux-demo-website.yaml
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageRepository
metadata:
name: flux-demo-website
namespace: flux-system
spec:
image: ghcr.io/talhajuikar/flux-demo-website
interval: 5m
provider: generic
Apply and verify:
1
2
3
4
5
6
7
8
9
10
kubectl apply -f clusters/my-cluster/imagerepositories/flux-demo-website.yaml
# Check status
kubectl get imagerepository -n flux-system
# NAME LAST SCAN TAGS
# flux-demo-website 2025-06-25T10:30:00Z 15
# Describe for details
kubectl describe imagerepository flux-demo-website -n flux-system
The .status.lastScanResult shows discovered tags and .status.canonicalImageName shows the resolved registry URL.
ImageRepository with Authentication
For private registries, create a Secret with credentials:
Docker Registry Secret
1
2
3
4
5
6
kubectl create secret docker-registry regcred \
--docker-server=https://index.docker.io/v1/ \
--docker-username=<your-username> \
--docker-password=<your-token> \
--docker-email=<your-email> \
-n flux-system
ImageRepository with Secret Reference
1
2
3
4
5
6
7
8
9
10
11
12
# clusters/my-cluster/imagerepositories/myapp.yaml
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageRepository
metadata:
name: myapp
namespace: flux-system
spec:
image: docker.io/myorg/myapp
interval: 10m
provider: generic
secretRef:
name: regcred
Provider-Specific Authentication
Flux supports cloud provider authentication using Workload Identity:
AWS ECR with IRSA
1
2
3
4
5
6
7
8
9
10
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageRepository
metadata:
name: myapp-ecr
namespace: flux-system
spec:
image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp
interval: 10m
provider: aws
serviceAccountName: image-reflector-controller
Patch the controller ServiceAccount with IAM role:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# clusters/my-cluster/flux-system/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- gotk-components.yaml
- gotk-sync.yaml
patches:
- patch: |-
apiVersion: v1
kind: ServiceAccount
metadata:
name: image-reflector-controller
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/flux-image-reflector
target:
kind: ServiceAccount
name: image-reflector-controller
Azure ACR with Workload Identity
1
2
3
4
5
6
7
8
9
10
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageRepository
metadata:
name: myapp-acr
namespace: flux-system
spec:
image: myregistry.azurecr.io/myapp
interval: 10m
provider: azure
serviceAccountName: image-reflector-controller
Patch for Azure Workload Identity:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
patches:
- patch: |-
apiVersion: v1
kind: ServiceAccount
metadata:
name: image-reflector-controller
annotations:
azure.workload.identity/client-id: <AZURE_CLIENT_ID>
labels:
azure.workload.identity/use: "true"
- patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: image-reflector-controller
labels:
azure.workload.identity/use: "true"
spec:
template:
metadata:
labels:
azure.workload.identity/use: "true"
Google GCR/Artifact Registry with Workload Identity
1
2
3
4
5
6
7
8
9
10
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageRepository
metadata:
name: myapp-gcr
namespace: flux-system
spec:
image: gcr.io/my-project/myapp
interval: 10m
provider: gcp
serviceAccountName: image-reflector-controller
Patch the ServiceAccount:
1
2
3
4
5
6
7
8
9
10
11
patches:
- patch: |-
apiVersion: v1
kind: ServiceAccount
metadata:
name: image-reflector-controller
annotations:
iam.gke.io/gcp-service-account: [email protected]
target:
kind: ServiceAccount
name: image-reflector-controller
Advanced ImageRepository Features
Exclude Tags with Regex
Exclude signature tags or specific patterns:
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageRepository
metadata:
name: myapp
namespace: flux-system
spec:
image: ghcr.io/myorg/myapp
interval: 5m
exclusionList:
- "^.*\\.sig$" # Exclude Cosign signatures
- "^.*-debug$" # Exclude debug builds
- "^.*-snapshot$" # Exclude snapshot builds
Cross-Namespace Access
Allow ImagePolicies in other namespaces to reference this ImageRepository:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageRepository
metadata:
name: shared-app
namespace: flux-system
spec:
image: ghcr.io/myorg/shared-app
interval: 5m
accessFrom:
namespaceSelectors:
- matchLabels:
environment: production
- matchLabels:
team: platform
ImagePolicy: Selecting the Latest Image
The ImagePolicy resource defines rules for selecting which image tag is considered “latest” from the scanned tags.
Semantic Versioning Policy
The most common policy for production workloads:
1
2
3
4
5
6
7
8
9
10
11
12
# clusters/my-cluster/imagepolicies/flux-demo-website.yaml
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImagePolicy
metadata:
name: flux-demo-website
namespace: flux-system
spec:
imageRepositoryRef:
name: flux-demo-website
policy:
semver:
range: 1.0.x
This selects the latest 1.0.x version (e.g., 1.0.3).
Common SemVer Ranges
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# Latest stable version (excluding pre-releases)
policy:
semver:
range: '>=1.0.0'
# Latest in major version 2
policy:
semver:
range: 2.x.x
# Latest in minor version 2.3
policy:
semver:
range: 2.3.x
# Latest patch in 2.3
policy:
semver:
range: '~2.3.0'
# Latest minor in major version 2
policy:
semver:
range: '^2.0.0'
# Include pre-releases (WARNING: will also select stable if available)
policy:
semver:
range: '^1.0.0-0'
# For pre-releases ONLY, use filterTags + alphabetical
filterTags:
pattern: '^[0-9]+\.[0-9]+\.[0-9]+-(alpha|beta)\.[0-9]+$'
policy:
alphabetical:
order: asc
Important: When using semver with range: '>=1.0.0-0', it will select any matching version including stable releases. Since SemVer considers 1.0.0 > 1.0.0-beta.3, stable versions will be preferred. To select only pre-release tags, use filterTags with alphabetical policy instead.
Alphabetical Policy
For tags sorted alphabetically (useful for timestamp-based tags):
1
2
3
4
5
6
7
8
9
10
11
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImagePolicy
metadata:
name: myapp-latest
namespace: flux-system
spec:
imageRepositoryRef:
name: myapp
policy:
alphabetical:
order: asc # or 'desc'
Numerical Policy
For numerical tags:
1
2
3
4
5
6
7
8
9
10
11
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImagePolicy
metadata:
name: myapp-build
namespace: flux-system
spec:
imageRepositoryRef:
name: myapp
policy:
numerical:
order: asc # or 'desc'
Filter Tags with Regex
Select only specific tags before applying the policy:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Select latest release candidate
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImagePolicy
metadata:
name: flux-demo-website-rc
namespace: flux-system
spec:
imageRepositoryRef:
name: flux-demo-website
filterTags:
pattern: '^1\.0\..*-rc\d+$'
policy:
semver:
range: 1.0.x-0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Select latest RELEASE.timestamp tag (MinIO style)
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImagePolicy
metadata:
name: minio
namespace: flux-system
spec:
imageRepositoryRef:
name: minio
filterTags:
pattern: '^RELEASE\.(?P<timestamp>.*)Z$'
extract: '$timestamp'
policy:
alphabetical:
order: asc
Digest Reflection
Track image digests for immutable deployments:
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImagePolicy
metadata:
name: myapp-digest
namespace: flux-system
spec:
imageRepositoryRef:
name: myapp
digestReflectionPolicy: IfNotPresent # Never, Always, IfNotPresent
policy:
semver:
range: 1.x.x
- Never: Don’t track digests (default)
- IfNotPresent: Track digest only when tag changes (recommended for immutable tags)
- Always: Always update digest, even if tag is the same (for mutable tags like
latest)
When using Always, you must specify interval:
1
2
3
4
5
6
7
8
spec:
digestReflectionPolicy: Always
interval: 1h
imageRepositoryRef:
name: myapp
policy:
alphabetical:
order: asc
Verify ImagePolicy
1
2
3
4
5
6
7
8
9
10
11
12
kubectl get imagepolicy -n flux-system
# NAME IMAGE TAG READY
# flux-demo-website ghcr.io/talhajuikar/flux-demo-website 1.0.0 True
kubectl describe imagepolicy flux-demo-website -n flux-system
# Status:
# Latest Ref:
# Image: ghcr.io/talhajuikar/flux-demo-website
# Tag: 1.0.0
# Digest: sha256:abc123...
ImageUpdateAutomation: Automating Git Updates
Now that we have ImageRepository scanning tags and ImagePolicy selecting the latest, we need ImageUpdateAutomation to update our Git repository.
Basic ImageUpdateAutomation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# clusters/my-cluster/imageupdateautomations/flux-system.yaml
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:
email: [email protected]
name: fluxcdbot
messageTemplate: |
Automated image update
Automation name: {{ .AutomationObject }}
Files:
{{ range $filename, $_ := .Changed.FileChanges -}}
- {{ $filename }}
{{ end -}}
Objects:
{{ range $resource, $changes := .Changed.Objects -}}
- {{ $resource.Kind }} {{ $resource.Name }}
Changes:
{{- range $_, $change := $changes }}
- {{ $change.OldValue }} -> {{ $change.NewValue }}
{{ end -}}
{{ end -}}
push:
branch: main
update:
path: ./clusters/my-cluster
strategy: Setters
How ImageUpdateAutomation Works
- Every
30m(interval), it checks out the Git repositorymainbranch - Scans all YAML files in
./clusters/my-clusterfor image policy markers - Updates image values based on ImagePolicy
.status.latestRef - Commits changes with the specified author and message
- Pushes to the
mainbranch - Flux detects the commit and reconciles the changes to the cluster
Marking Images for Update
To tell ImageUpdateAutomation which fields to update, add markers in comments:
Deployment with Full Image
1
2
3
4
5
6
7
8
9
10
11
apiVersion: apps/v1
kind: Deployment
metadata:
name: flux-demo-website
namespace: default
spec:
template:
spec:
containers:
- name: flux-demo-website
image: ghcr.io/talhajuikar/flux-demo-website:1.0.0 # {"$imagepolicy": "flux-system:flux-demo-website"}
The marker format: {"$imagepolicy": "<namespace>:<policy-name>"}
HelmRelease with Separate Fields
1
2
3
4
5
6
7
8
9
10
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: flux-demo-website
namespace: default
spec:
values:
image:
repository: ghcr.io/talhajuikar/flux-demo-website # {"$imagepolicy": "flux-system:flux-demo-website:name"}
tag: 1.0.0 # {"$imagepolicy": "flux-system:flux-demo-website:tag"}
The field marker format: {"$imagepolicy": "<namespace>:<policy-name>:<field>"}
Available fields:
:name- Full image path without tag (e.g.,ghcr.io/org/app):tag- Just the tag (e.g.,1.2.3):digest- Just the digest (e.g.,sha256:abc123...)
With Digest Pinning
1
2
3
4
5
6
7
8
9
10
11
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: flux-demo-website
namespace: default
spec:
values:
image:
repository: ghcr.io/talhajuikar/flux-demo-website # {"$imagepolicy": "flux-system:flux-demo-website:name"}
tag: 1.0.0 # {"$imagepolicy": "flux-system:flux-demo-website:tag"}
digest: sha256:abc123... # {"$imagepolicy": "flux-system:flux-demo-website:digest"}
For digest to be available, the ImagePolicy must have digestReflectionPolicy: IfNotPresent or Always.
Advanced Git Configuration
Push to Different Branch
Useful for creating pull requests:
1
2
3
4
5
6
7
8
9
10
11
spec:
git:
checkout:
ref:
branch: main
commit:
author:
email: [email protected]
name: fluxcdbot
push:
branch: flux-image-updates
This creates/updates the flux-image-updates branch with each update.
Using Refspec
For advanced Git workflows like Gerrit or creating PRs in Gitea/Forgejo:
1
2
3
4
5
6
7
spec:
git:
checkout:
ref:
branch: main
push:
refspec: refs/heads/main:refs/for/main
Commit Message Template with Values
1
2
3
4
5
6
7
8
9
10
11
12
spec:
git:
commit:
messageTemplate: |
[ci skip] Update images for cluster {{ .Values.clusterName }}
{{ range $resource, $changes := .Changed.Objects -}}
- {{ $resource.Kind }}/{{ $resource.Name }}: {{ range $_, $change := $changes }}{{ $change.NewValue }}{{ end }}
{{ end -}}
messageTemplateValues:
clusterName: production
Template functions available include 70+ functions from Go templates and Sprig.
Signed Commits
Sign commits with GPG:
1
2
3
4
5
# Create GPG key secret
kubectl create secret generic git-signing-key \
--from-file=git.asc=<path-to-gpg-key> \
--from-literal=passphrase=<key-passphrase> \
-n flux-system
1
2
3
4
5
6
7
8
9
spec:
git:
commit:
author:
email: [email protected]
name: fluxcdbot
signingKey:
secretRef:
name: git-signing-key
Push Options for GitLab MR
1
2
3
4
5
6
7
8
spec:
git:
push:
branch: flux-updates
options:
merge_request.create: ""
merge_request.target: main
merge_request.title: "Automated image updates"
Push Options for Gitea/Forgejo PR
1
2
3
4
5
6
7
8
9
spec:
git:
push:
branch: flux-updates
refspec: refs/heads/flux-updates:refs/for/main
options:
topic: flux-updates
title: "Flux Image Update"
description: "This PR is automatically opened by flux"
Policy Selector
Limit which ImagePolicies an ImageUpdateAutomation considers:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageUpdateAutomation
metadata:
name: production-apps
namespace: flux-system
spec:
interval: 30m
sourceRef:
kind: GitRepository
name: flux-system
policySelector:
matchLabels:
environment: production
git:
# ... git config
Then label your ImagePolicies:
1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImagePolicy
metadata:
name: flux-demo-website-prod
namespace: flux-system
labels:
environment: production
spec:
imageRepositoryRef:
name: flux-demo-website
policy:
semver:
range: 1.x.x
Or use matchExpressions:
1
2
3
4
5
6
7
8
spec:
policySelector:
matchExpressions:
- key: app.kubernetes.io/component
operator: In
values:
- frontend
- backend
Monitoring ImageUpdateAutomation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Check status
kubectl get imageupdateautomation -n flux-system
# NAME LAST RUN
# flux-system 2025-06-25T14:22:34Z
# Describe for details
kubectl describe imageupdateautomation flux-system -n flux-system
# Status:
# Last Automation Run Time: 2025-06-25T14:22:34Z
# Last Push Commit: abc123def456...
# Last Push Time: 2025-06-25T14:22:34Z
# Observed Policies:
# Flux-Demo-Website:
# Name: ghcr.io/talhajuikar/flux-demo-website
# Tag: 1.0.0
Trigger Manual Update
1
2
3
4
5
6
7
8
# Using kubectl
kubectl annotate --overwrite \
imageupdateautomation/flux-system \
reconcile.fluxcd.io/requestedAt="$(date +%s)" \
-n flux-system
# Using flux CLI
flux reconcile image update flux-system -n flux-system
Complete Example: Multi-Environment Setup
Let’s create a complete example with separate ImagePolicies for dev, staging, and production:
Directory Structure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
flux-demo-website/
├── .github/
│ └── workflows/
│ └── build.yml
├── apps/
│ └── demo-website/
│ ├── base/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ ├── ingress.yaml
│ │ └── kustomization.yaml
│ └── overlays/
│ ├── dev/
│ ├── staging/
│ └── prod/
└── clusters/
└── flux-demo/
├── flux-system/
├── apps.yaml
└── image-automation/ # New directory
├── kustomization.yaml # Flux will pick this up
├── flux-demo-website-repository.yaml
├── flux-demo-website-policy-dev.yaml
├── flux-demo-website-policy-staging.yaml
├── flux-demo-website-policy-prod.yaml
└── image-update-automation.yaml
Kustomization Resource (So Flux Picks Up Image Automation)
First, create a Kustomization resource so Flux knows to reconcile these manifests:
1
2
3
4
5
6
7
8
9
# clusters/flux-demo/image-automation/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- flux-demo-website-repository.yaml
- flux-demo-website-policy-dev.yaml
- flux-demo-website-policy-staging.yaml
- flux-demo-website-policy-prod.yaml
- image-update-automation.yaml
Then create a Flux Kustomization to tell Flux to reconcile this directory:
1
2
3
4
5
6
7
8
9
10
11
12
13
# clusters/flux-demo/image-automation.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: image-automation
namespace: flux-system
spec:
interval: 10m
path: ./clusters/flux-demo/image-automation
prune: true
sourceRef:
kind: GitRepository
name: flux-system
ImageRepository
1
2
3
4
5
6
7
8
9
10
# clusters/flux-demo/image-automation/flux-demo-website-repository.yaml
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageRepository
metadata:
name: flux-demo-website
namespace: flux-system
spec:
image: ghcr.io/talhajuikar/flux-demo-website
interval: 5m
provider: generic
ImagePolicies for Each Environment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# clusters/flux-demo/image-automation/flux-demo-website-policy-dev.yaml
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImagePolicy
metadata:
name: flux-demo-website-dev
namespace: flux-system
labels:
environment: dev
spec:
imageRepositoryRef:
name: flux-demo-website
filterTags:
pattern: '^[0-9]+\.[0-9]+\.[0-9]+-(alpha|beta)\.[0-9]+$'
extract: '$0'
policy:
alphabetical:
order: asc # Latest alpha/beta tag alphabetically
---
# clusters/flux-demo/image-automation/flux-demo-website-policy-staging.yaml
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImagePolicy
metadata:
name: flux-demo-website-staging
namespace: flux-system
labels:
environment: staging
spec:
imageRepositoryRef:
name: flux-demo-website
filterTags:
pattern: '^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$'
policy:
semver:
range: '>=1.0.0-0' # Release candidates only
---
# clusters/flux-demo/image-automation/flux-demo-website-policy-prod.yaml
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImagePolicy
metadata:
name: flux-demo-website-prod
namespace: flux-system
labels:
environment: production
spec:
imageRepositoryRef:
name: flux-demo-website
digestReflectionPolicy: IfNotPresent
policy:
semver:
range: 1.x.x # Stable releases only
Deployments with Markers
You have two options for adding image policy markers:
Option 1: Using images in kustomization.yaml (Recommended)
This is cleaner and doesn’t require separate patch files. Update your overlay kustomization.yaml files:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# apps/demo-website/overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: dev
resources:
- ../../base
patchesStrategicMerge:
- replica-patch.yaml
- resource-patch.yaml
- config-patch.yaml
- ingress-patch.yaml
# Add the image override with policy marker
images:
- name: ghcr.io/talhajuikar/flux-demo-website
newTag: 1.0.0-alpha.1 # {"$imagepolicy": "flux-system:flux-demo-website-dev"}
commonLabels:
environment: dev
nameSuffix: -dev
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# apps/demo-website/overlays/staging/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: staging
resources:
- ../../base
patchesStrategicMerge:
- replica-patch.yaml
- resource-patch.yaml
- config-patch.yaml
- ingress-patch.yaml
images:
- name: ghcr.io/talhajuikar/flux-demo-website
newTag: 1.0.0-rc.1 # {"$imagepolicy": "flux-system:flux-demo-website-staging"}
commonLabels:
environment: staging
nameSuffix: -staging
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# apps/demo-website/overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: production
resources:
- ../../base
patchesStrategicMerge:
- replica-patch.yaml
- resource-patch.yaml
- config-patch.yaml
- ingress-patch.yaml
images:
- name: ghcr.io/talhajuikar/flux-demo-website
newTag: 1.0.0 # {"$imagepolicy": "flux-system:flux-demo-website-prod"}
commonLabels:
environment: production
nameSuffix: -prod
Important Notes:
- The
namefield must exactly match the image name in your base deployment (without tag) - Do NOT include
newNamefield - only use it if you need to change the registry - Flux will update only the
newTagvalue based on the ImagePolicy - The policy marker comment must be on the same line as
newTag
Option 2: Using Deployment Patches (Alternative)
If you prefer separate patch files, create deployment patches:
1
2
3
4
5
6
7
8
9
10
11
# apps/demo-website/overlays/dev/deployment-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: flux-demo-website
spec:
template:
spec:
containers:
- name: demo-website
image: ghcr.io/talhajuikar/flux-demo-website:1.0.0-alpha.1 # {"$imagepolicy": "flux-system:flux-demo-website-dev"}
Then reference it in your kustomization.yaml:
1
2
3
4
5
6
patchesStrategicMerge:
- replica-patch.yaml
- resource-patch.yaml
- config-patch.yaml
- ingress-patch.yaml
- deployment-patch.yaml # Add this
ImageUpdateAutomation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# clusters/flux-demo/image-automation/image-update-automation.yaml
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageUpdateAutomation
metadata:
name: flux-demo-website
namespace: flux-system
spec:
interval: 30m
sourceRef:
kind: GitRepository
name: flux-system
git:
checkout:
ref:
branch: main
commit:
author:
email: [email protected]
name: fluxcdbot
messageTemplate: |
[ci skip] Update flux-demo-website images
{{ range $filename, $_ := .Changed.FileChanges -}}
- {{ $filename }}
{{ end -}}
{{ range $resource, $changes := .Changed.Objects -}}
{{ $resource.Namespace }}/{{ $resource.Kind }}/{{ $resource.Name }}
{{- range $_, $change := $changes }}
{{ $change.OldValue }} -> {{ $change.NewValue }}
{{ end -}}
{{ end -}}
push:
branch: main
update:
path: ./apps/demo-website # Scans this directory recursively for image policy markers
strategy: Setters
Important: The path field tells Flux where to look for image policy markers. With strategy: Setters, Flux will:
- Recursively scan
./apps/demo-websitedirectory and all subdirectories - Find all files with
# {"$imagepolicy": ...}markers (including inoverlays/dev/,overlays/staging/,overlays/prod/) - Update the image tags in those files based on the ImagePolicy status
- Commit and push the changes
Apply Resources
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# First, apply the Flux Kustomization so Flux picks up the image-automation directory
kubectl apply -f clusters/flux-demo/image-automation.yaml
# Or apply the resources directly
kubectl apply -f clusters/flux-demo/image-automation/
# Verify image automation resources are created
kubectl get imagerepository,imagepolicy,imageupdateautomation -n flux-system
# Wait for first scan and update
flux reconcile image repository flux-demo-website -n flux-system
flux reconcile image update flux-demo-website -n flux-system
# Check the status
kubectl describe imagerepository flux-demo-website -n flux-system
kubectl describe imagepolicy flux-demo-website-prod -n flux-system
kubectl describe imageupdateautomation flux-demo-website -n flux-system
Best Practices for Production
1. Use Semantic Versioning
Tag your container images with proper semantic versions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# In your CI/CD pipeline (GitHub Actions)
# The workflow automatically builds from git tags
# Create tags for different environments:
# Stable release (for production)
git tag v1.0.0
git push origin v1.0.0
# Alpha releases (for dev environment)
git tag v1.0.1-alpha.1
git push origin v1.0.1-alpha.1
git tag v1.1.0-alpha.2
git push origin v1.1.0-alpha.2
# Release candidates (for staging environment)
git tag v1.0.1-rc.1
git push origin v1.0.1-rc.1
git tag v1.1.0-rc.2
git push origin v1.1.0-rc.2
# Beta releases
git tag v2.0.0-beta.1
git push origin v2.0.0-beta.1
How ImagePolicies Pick Them Up:
The ImagePolicy for each environment automatically selects the appropriate version:
- Dev Policy (
range: '>=1.0.0-0'): Picks the latest from ALL versions including-alpha.X,-beta.X,-rc.X, and stable releases - Staging Policy (with
pattern: '^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$'): Only picks-rc.X(release candidate) tags - Production Policy (
range: 1.x.x): Only picks stable releases without pre-release identifiers
Example flow:
- Push
v1.0.1-alpha.1→ Dev environment gets updated - Push
v1.0.1-rc.1→ Staging environment gets updated - Push
v1.0.1→ Production environment gets updated
2. Separate Policies Per Environment
- Dev:
range: '>=1.0.0-0'(all versions including pre-releases) - Staging:
range: '>=1.0.0-rc.0'(release candidates) - Production:
range: 1.x.x(stable releases only)
3. Use Digest Pinning for Production
1
2
3
4
5
spec:
digestReflectionPolicy: IfNotPresent
policy:
semver:
range: 1.x.x
This ensures immutable deployments even if a tag is overwritten.
4. Set Appropriate Intervals
1
2
3
4
5
# Image scanning (ImageRepository)
interval: 5m # Frequent for fast feedback
# Git updates (ImageUpdateAutomation)
interval: 30m # Less frequent to batch updates
5. Use Separate ImageUpdateAutomation for Each Environment
Instead of one automation for all environments:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Production automation (more cautious)
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageUpdateAutomation
metadata:
name: production-updates
namespace: flux-system
spec:
interval: 1h
policySelector:
matchLabels:
environment: production
git:
push:
branch: prod-image-updates # Create PR instead of direct push
---
# Dev automation (more aggressive)
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageUpdateAutomation
metadata:
name: dev-updates
namespace: flux-system
spec:
interval: 15m
policySelector:
matchLabels:
environment: dev
git:
push:
branch: main # Direct push to main
6. Exclude Unwanted Tags
1
2
3
4
5
6
spec:
exclusionList:
- "^.*\\.sig$" # Cosign signatures
- "^.*-debug$" # Debug images
- "^.*-snapshot$" # Snapshot builds
- "^sha-[0-9a-f]+$" # Git SHA tags
7. Monitor Automation Status
Set up alerts for failed image updates:
1
2
3
4
5
6
7
8
9
10
11
12
13
# Using Flux notification controller
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
name: image-update-alerts
namespace: flux-system
spec:
eventSeverity: error
eventSources:
- kind: ImageUpdateAutomation
name: '*'
providerRef:
name: slack
8. Use Commit Message Templates with Skip CI
Prevent infinite CI loops:
1
2
3
4
5
6
7
8
9
10
spec:
git:
commit:
messageTemplate: |
[ci skip] Automated image update
{{ range $resource, $changes := .Changed.Objects -}}
{{ $resource.Kind }}/{{ $resource.Name }}: {{ range $change := $changes }}{{ $change.NewValue }}{{ end }}
{{ end -}}
9. Secure Your Automation
- Use dedicated ServiceAccounts with minimal permissions
- Rotate registry credentials regularly
- Use Workload Identity when possible (AWS IRSA, Azure Workload Identity, GCP Workload Identity)
- Audit Git commits from automation
10. Test Image Updates
Before enabling automation in production:
1
2
3
4
5
6
# Manually trigger to see what would change
flux reconcile image repository flux-demo-website -n flux-system
flux reconcile image update flux-system -n flux-system --dry-run
# Check ImagePolicy status
kubectl get imagepolicy flux-demo-website-prod -n flux-system -o jsonpath='{.status.latestRef}'
Troubleshooting
ImageRepository Not Scanning
1
2
3
4
5
6
7
8
9
10
# Check ImageRepository events
kubectl describe imagerepository flux-demo-website -n flux-system
# Common issues:
# - Authentication failure (check secret)
# - Invalid image name
# - Network issues
# Check controller logs
kubectl logs -n flux-system deploy/image-reflector-controller -f
ImagePolicy Not Selecting Latest
1
2
3
4
5
6
7
8
9
10
# Check policy status
kubectl describe imagepolicy flux-demo-website-prod -n flux-system
# Verify tags are being scanned
kubectl get imagerepository flux-demo-website -n flux-system -o jsonpath='{.status.lastScanResult.latestTags}'
# Common issues:
# - No tags match the semver range
# - Regex filter excludes all tags
# - ImageRepository not ready
ImageUpdateAutomation Not Committing
1
2
3
4
5
6
7
8
9
10
11
# Check automation status
kubectl describe imageupdateautomation flux-system -n flux-system
# Common issues:
# - No image policy markers in manifests
# - Git authentication failure
# - Branch protection rules prevent push
# - Path doesn't match YAML files location
# Check controller logs
kubectl logs -n flux-system deploy/image-automation-controller -f
No Updates Being Applied
1
2
3
4
5
6
7
8
9
10
11
# Verify marker syntax
grep -r '{"$imagepolicy"' apps/demo-website/
# Should look like:
# {"$imagepolicy": "flux-system:flux-demo-website-prod"}
# {"$imagepolicy": "flux-system:flux-demo-website-prod:tag"}
# Common issues:
# - Wrong namespace in marker
# - Typo in policy name
# - Comment syntax incorrect
Conclusion
Flux Image Automation provides a powerful, GitOps-native way to keep your Kubernetes workloads up-to-date with the latest container images. By combining:
- ImageRepository for registry scanning
- ImagePolicy for version selection
- ImageUpdateAutomation for Git updates
You can create fully automated deployment pipelines that maintain the Git repository as the single source of truth.
Key takeaways:
✅ Use semantic versioning for proper version selection
✅ Separate policies per environment (dev, staging, prod)
✅ Enable digest pinning for production immutability
✅ Set appropriate scan and update intervals
✅ Use markers to tell Flux which fields to update
✅ Leverage Workload Identity for cloud registries
✅ Monitor automation status and set up alerts
✅ Test thoroughly before enabling in production
In the next part, we’ll explore Flux notifications and integrations with monitoring systems to create a complete observability solution for your GitOps workflows.
