Orchestration

Automate Flux CD Image Updates. Deploy Faster.

July 1, 2026 Kubezilla Team 16 min read

Introduction

In the fast-paced world of microservices and continuous delivery, keeping your Kubernetes deployments updated with the latest container images is a critical yet often manual and error-prone task. Developers push new code, CI pipelines build fresh images, and then someone has to remember to update the image tag in the Kubernetes YAML. This can lead to deployment delays, inconsistencies, and even production outages if not handled meticulously. Imagine a scenario where a critical security patch is available, but it takes hours or even days to propagate to your clusters because of manual update processes.

Enter GitOps and tools like Flux CD. While Flux excels at synchronizing your Kubernetes cluster state with a Git repository, it traditionally relies on you to manually update the image tags within your Git-managed YAML files. This still leaves a gap in the automation chain. What if Flux could automatically detect new container image versions in your registry, update the corresponding YAML in Git, and then apply those changes to your cluster? This is precisely where Flux CD’s Image Automation controllers shine, providing a powerful, end-to-end GitOps workflow that truly automates the update process from image build to deployment.

This guide will walk you through setting up Flux CD Image Automation to automatically detect and update container images in your Kubernetes deployments. We’ll cover the core components, configuration, and best practices to ensure your applications are always running the latest tested versions without manual intervention. By the end of this tutorial, you’ll have a robust system that observes your image registries, updates your Git repository, and synchronizes your clusters, bringing you closer to a fully autonomous and resilient CI/CD pipeline.

Prerequisites

Before diving into Flux CD Image Automation, ensure you have the following:

  • A Kubernetes Cluster: Any version 1.20+ will work. You can use Minikube, Kind, a cloud provider (EKS, GKE, AKS), or a self-managed cluster.
  • kubectl CLI: Installed and configured to connect to your Kubernetes cluster. Refer to the official Kubernetes documentation for installation instructions.
  • flux CLI: The Flux CD command-line interface. Install it by following the instructions on the Flux CD website.
  • Git Repository: A Git repository (GitHub, GitLab, Bitbucket, Azure DevOps) to store your Kubernetes manifests. This will be the source of truth for your cluster configuration. You’ll need credentials (SSH key or personal access token) to allow Flux to access and commit to this repository.
  • Container Registry: A container registry (Docker Hub, Google Container Registry, AWS ECR, Quay.io) where your application images are stored. Flux will need read access to this registry to discover new image versions.
  • Basic Understanding of GitOps: Familiarity with GitOps principles and how Flux CD operates is beneficial. If you’re new to GitOps, we recommend checking out the Flux CD concepts documentation.

Step-by-Step Guide to Flux CD Image Automation

Step 1: Bootstrap Flux CD with Image Automation Controllers

First, you need to install Flux CD on your Kubernetes cluster and ensure the image automation controllers are enabled. These controllers are responsible for monitoring image repositories and updating Git. When you bootstrap Flux, you can specify extra components to include.

# Replace with your Git repository details
GIT_USER="your-git-username"
GIT_REPO="your-git-repo"
GIT_BRANCH="main"
GIT_PATH="clusters/my-cluster" # Path within your repo for Flux manifests

# Bootstrap Flux with the image-reflector-controller and image-automation-controller
flux bootstrap git \
  --owner=${GIT_USER} \
  --repository=${GIT_REPO} \
  --branch=${GIT_BRANCH} \
  --path=${GIT_PATH} \
  --personal \
  --components-extra=image-reflector-controller,image-automation-controller

# Verify Flux installation and controllers
kubectl get pods -n flux-system

Explanation:
The flux bootstrap git command performs the initial setup. It installs the Flux controllers into the flux-system namespace on your cluster and configures them to synchronize with your specified Git repository. The --personal flag is used for personal Git hosting (like GitHub Personal Access Tokens). For organization-level repositories, you might use SSH keys. Crucially, --components-extra=image-reflector-controller,image-automation-controller ensures that the necessary controllers for image automation are deployed alongside the core Flux components. The image-reflector-controller observes container registries, and the image-automation-controller performs the Git commits.

After bootstrapping, Flux will create a directory structure in your Git repository (e.g., clusters/my-cluster) containing its own manifests. You can verify the installation by checking the pods in the flux-system namespace. You should see pods for the source controller, kustomize controller, helm controller, notification controller, and importantly, the image-reflector-controller and image-automation-controller.

Verify:

kubectl get pods -n flux-system

Expected Output:

NAME                                             READY   STATUS    RESTARTS   AGE
helm-controller-74b886d99f-sx89m                 1/1     Running   0          2m
image-automation-controller-6789f6d764-xyzab     1/1     Running   0          2m
image-reflector-controller-5b7d7f76c-uvwxy       1/1     Running   0          2m
kustomize-controller-745d6f854c-abcde            1/1     Running   0          2m
notification-controller-5c6d5b78f8-fghij         1/1     Running   0          2m
source-controller-658f8b74c-klmno                1/1     Running   0          2m

Step 2: Define an ImageRepository

The ImageRepository custom resource tells Flux which container registry and image it should monitor for new versions. This acts as the source of image information.

# ./clusters/my-cluster/image-repository.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
  name: my-nginx-image
  namespace: flux-system # Or the namespace where your application lives
spec:
  image: nginx # The image name in your registry (e.g., docker.io/library/nginx)
  interval: 1m # How often to scan the registry for new tags
  secretRef: # Optional: if your registry requires authentication
    name: my-registry-creds
# Apply the ImageRepository manifest
kubectl apply -f ./clusters/my-cluster/image-repository.yaml

# Commit and push to your Git repository
git add .
git commit -m "Add ImageRepository for nginx"
git push origin main

Explanation:
The ImageRepository object is fundamental. It specifies the full image name (e.g., nginx, myregistry.com/myorg/myapp) that Flux should track. The interval field defines how frequently Flux’s image-reflector-controller will poll the registry for new image tags. A shorter interval means faster detection but more frequent registry calls. If your container registry requires authentication (e.g., private registries), you can specify a secretRef pointing to a Kubernetes Secret containing your registry credentials. This secret should be of type kubernetes.io/dockerconfigjson or kubernetes.io/basic-auth. For a deeper dive into secrets management, consider exploring solutions like Kubernetes Secrets or external secret stores.

Verify:

kubectl get imagerepository my-nginx-image -n flux-system -o yaml

Expected Output (snippet):

status:
  canonicalImage: 'nginx'
  lastScanTime: "2023-10-27T10:30:00Z"
  latestImage: 'nginx:1.25.3' # Or whatever the latest tag is
  observedGeneration: 1
  conditions:
  - lastTransitionTime: "2023-10-27T10:30:00Z"
    message: Image repository scanned successfully
    reason: Succeeded
    status: "True"
    type: Ready

Look for status.latestImage to see the latest image detected and status.conditions to confirm it’s ready.

Step 3: Define an ImagePolicy

An ImagePolicy defines the rules for selecting the “latest” image tag from the versions discovered by an ImageRepository. This allows you to specify versioning schemes like SemVer, alphabetical, or timestamp-based.

# ./clusters/my-cluster/image-policy.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: my-nginx-policy
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: my-nginx-image # References the ImageRepository created above
  policy:
    semver:
      range: '>=1.20.0 <1.26.0' # Example: only consider versions greater than or equal to 1.20.0 and less than 1.26.0
    # alphabetical:
    #   order: asc # Or desc
    # numerical:
    #   order: asc
    #   field: 'tag' # For tags like 'v1', 'v2'
# Apply the ImagePolicy manifest
kubectl apply -f ./clusters/my-cluster/image-policy.yaml

# Commit and push to your Git repository
git add .
git commit -m "Add ImagePolicy for nginx"
git push origin main

Explanation:
The ImagePolicy acts as a filter on the image tags found by the ImageRepository. The imageRepositoryRef links it to a specific ImageRepository. The policy section is where you define your selection logic. Common policies include semver (Semantic Versioning), alphabetical, or numerical. For instance, semver: range: '>=1.20.0 <1.26.0' would select the highest patch/minor version within that major range. This is crucial for managing releases and preventing unintended upgrades to major versions. For more advanced versioning strategies, you can refer to the Flux CD Image Policy documentation.

Verify:

kubectl get imagepolicy my-nginx-policy -n flux-system -o yaml

Expected Output (snippet):

status:
  latestImage: 'nginx:1.25.3' # The latest image tag selected by the policy
  observedGeneration: 1
  conditions:
  - lastTransitionTime: "2023-10-27T10:35:00Z"
    message: Image policy evaluated successfully
    reason: Succeeded
    status: "True"
    type: Ready

Confirm that status.latestImage shows the desired image selected by your policy.

Step 4: Create a Sample Deployment to Automate

Now, let’s create a simple Kubernetes Deployment that we want Flux to automatically update. This Deployment will initially use a specific `nginx` image tag.

# ./clusters/my-cluster/nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-auto
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-auto
  template:
    metadata:
      labels:
        app: nginx-auto
    spec:
      containers:
      - name: nginx
        image: nginx:1.20.2 # This is the tag Flux will update
        ports:
        - containerPort: 80
# Apply the Deployment manifest
kubectl apply -f ./clusters/my-cluster/nginx-deployment.yaml

# Commit and push to your Git repository
git add .
git commit -m "Add initial nginx-auto deployment"
git push origin main

Explanation:
This is a standard Kubernetes Deployment. The crucial part for image automation is the image: nginx:1.20.2 line. This is the value that Flux will modify directly in your Git repository. It’s important to use a specific tag here, even if it’s an older one, so Flux has something to update. Never use :latest with production deployments; it’s non-deterministic and can lead to unexpected rollouts. For secure deployments, consider integrating image signing with tools like Sigstore and Kyverno to ensure images are trusted before deployment.

Verify:

kubectl get deployment nginx-auto -n default

Expected Output:

NAME         READY   UP-TO-DATE   AVAILABLE   AGE
nginx-auto   1/1     1            1           1m

Step 5: Define an ImageUpdateAutomation

The ImageUpdateAutomation custom resource is the orchestrator. It tells Flux which Git repository to update, which files to modify, and how to commit the changes. It links the ImagePolicy to your Git repository.

# ./clusters/my-cluster/image-update-automation.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: update-nginx-deployment
  namespace: flux-system
spec:
  interval: 1m # How often to check for image updates and commit to Git
  git:
    checkout:
      ref:
        branch: main # The branch to checkout
    commit:
      author: Flux CD Bot <fluxcd-bot@example.com>
      messageTemplate: "Update nginx-auto image to {{ .LatestImage }}" # Commit message template
    push:
      branch: main # The branch to push to
  update:
    path: ./clusters/my-cluster # The path within the Git repo to scan for Kubernetes manifests
    strategy: Setters # Or 'Flux'
# Apply the ImageUpdateAutomation manifest
kubectl apply -f ./clusters/my-cluster/image-update-automation.yaml

# Commit and push to your Git repository
git add .
git commit -m "Add ImageUpdateAutomation for nginx"
git push origin main

Explanation:
The ImageUpdateAutomation resource is where the magic happens. It defines the workflow:

  • interval: How often Flux should run the automation, checking for new images and committing to Git.
  • git.checkout.ref.branch: The branch Flux will clone to perform updates.
  • git.commit: Details for the Git commit, including author and a template for the commit message. {{ .LatestImage }} is a placeholder that Flux will replace with the actual new image tag.
  • git.push.branch: The branch to push the changes to. This should typically be the same as the checkout branch.
  • update.path: The directory in your Git repository where Flux should look for Kubernetes manifests to update.
  • update.strategy: This specifies how Flux should update the image tag. The most common and recommended strategy is Setters, which uses Kustomize setters syntax. The alternative Flux strategy relies on specific YAML comments. We’ll use Setters.

This resource instructs Flux’s image-automation-controller to watch the specified path, identify image tags that need updating based on ImagePolicy, and then commit those changes back to the Git repository. Once committed, the Flux kustomize-controller (or helm-controller) will detect the Git change and apply it to the cluster.

Verify:

kubectl get imageupdateautomation update-nginx-deployment -n flux-system -o yaml

Expected Output (snippet):

status:
  lastAutomationRunTime: "2023-10-27T10:40:00Z"
  observedGeneration: 1
  conditions:
  - lastTransitionTime: "2023-10-27T10:40:00Z"
    message: Update automation is ready
    reason: Succeeded
    status: "True"
    type: Ready

Step 6: Annotate Your Deployment for Image Automation

For Flux to know which image in your Deployment to update, you need to add specific annotations to the container specification in your Kubernetes manifest.

We’re using the Setters strategy, which typically involves a comment in the YAML to mark the setter. However, for direct image replacement, Flux can also use annotations. Let’s use the annotation method for simplicity in this example, which is often used in conjunction with the Flux strategy but can also work with Setters if configured correctly or if the image field is simply replaced.

Correction: For strategy: Setters, the standard way is to use Kustomize setters syntax, which means your YAML might look like: image: nginx:1.20.2 # {"$imagepolicy": "flux-system:my-nginx-policy"}. However, Flux also supports a simpler annotation-based approach for direct image field replacement.

Let’s update the nginx-deployment.yaml to include the necessary annotation. The annotation tells Flux which ImagePolicy to use for this specific image.

# ./clusters/my-cluster/nginx-deployment.yaml (updated)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-auto
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-auto
  template:
    metadata:
      labels:
        app: nginx-auto
      annotations: # Optional: to track the last update reason
        kubernetes.io/change-cause: "Image update automation"
    spec:
      containers:
      - name: nginx
        image: nginx:1.20.2 # This is the tag Flux will update
        # Add the annotation that links this image field to the ImagePolicy
        # The key is 'image.fluxcd.io/'
        # The value specifies the policy to apply (e.g., 'semver:>=1.20.0')
        # OR, more simply, it can point directly to the ImagePolicy object:
        # image.fluxcd.io/my-nginx-image: flux-system:my-nginx-policy
        # For the 'Setters' strategy, Flux will automatically replace the image tag
        # if it finds a match. Adding the policy ref helps Flux know WHICH policy to apply.
        # Let's simplify this to rely on the ImageUpdateAutomation's path and the ImagePolicy's ref.
        # A more explicit way for 'Setters' strategy is:
        # image: nginx:1.20.2 # {"$imagepolicy": "flux-system:my-nginx-policy"}
        # But Flux's image automation controller can also infer this if the ImagePolicy points to ImageRepository.
        # For the purpose of this guide, let's use the explicit setter comment.
        # This comment is crucial for the 'Setters' strategy.
        # The value inside {"$imagepolicy": "..."} should be 'namespace:imagepolicy-name'
        # Let's assume the ImagePolicy is in 'flux-system' namespace.
        image: nginx:1.20.2 # {"$imagepolicy": "flux-system:my-nginx-policy"}
        ports:
        - containerPort: 80
# Apply the updated Deployment manifest
kubectl apply -f ./clusters/my-cluster/nginx-deployment.yaml

# Commit and push to your Git repository
git add .
git commit -m "Annotate nginx-auto deployment for image automation"
git push origin main

Explanation:
The comment # {"$imagepolicy": "flux-system:my-nginx-policy"} is a Kustomize setter. When Flux’s image-automation-controller processes the files in the update.path, it looks for these special comments. It then uses the referenced ImagePolicy (my-nginx-policy in the flux-system namespace) to determine the latest image tag. Once a newer tag is found, it updates the image field in the YAML, creates a Git commit, and pushes it back to your repository. This closes the loop: registry -> policy -> Git update -> cluster sync.

Verify:
Wait for a few minutes (based on your ImageRepository and ImageUpdateAutomation intervals).
Check your Git repository’s commit history. You should see a new commit from “Flux CD Bot” with a message like “Update nginx-auto image to nginx:1.25.3” (or whatever the latest image is according to your policy).

Then, check the deployed image in your cluster:

kubectl get deployment nginx-auto -n default -o jsonpath='{.spec.template.spec.containers[0].image}'

Expected Output:

nginx:1.25.3 # Or the latest image as per your policy

You can also check the Flux logs:

kubectl logs -f -n flux-system deploy/image-automation-controller

You should see messages indicating that it’s reconciling and applying updates.

Production Considerations

  • Image Tagging Strategy: A robust image tagging strategy is paramount. Use Semantic Versioning (SemVer) (e.g., v1.2.3) consistently. Avoid :latest in production as it’s non-deterministic. Consider using Git SHA or build numbers for unique, immutable tags.
  • Rate Limiting: Be mindful of the interval configured for ImageRepository and ImageUpdateAutomation. Too frequent polling can hit API rate limits on your container registry or Git provider. Start with longer intervals (e.g., 5-10 minutes) and adjust as needed.
  • Authentication: For private registries or Git repositories, ensure your Kubernetes Secrets (referenced by ImageRepository and Flux’s Git repository configuration) are securely managed. Consider using a Secret management solution like HashiCorp Vault or cloud-native secret stores.
  • Rollback Strategy: While automation is great, things can go wrong. Ensure you have a clear rollback strategy. Since Flux updates Git, rolling back means reverting the commit in Git. This will trigger Flux to revert the cluster state.
  • Testing and Staging Environments: Implement image automation in your staging or pre-production environments first. Use different ImagePolicy ranges for different environments (e.g., allow only patch updates in production, minor updates in staging).
  • Notifications: Integrate Flux with notification systems (Slack, Teams, webhooks) to get alerts when image updates occur or when automation fails. This can be configured using Flux’s Provider and Alert resources.
  • Security Scans: Ensure your CI pipeline includes robust security scanning (e.g., Trivy, Clair) for container images before they are pushed to the registry. Image automation will quickly deploy vulnerable images if not caught upstream. For advanced security, explore securing your supply chain with tools like Sigstore and Kyverno.
  • Resource Limits: Set appropriate CPU and memory limits for Flux controllers to prevent them from consuming excessive cluster resources.
  • Network Policies: If your cluster uses Kubernetes Network Policies, ensure that the flux-system namespace (or wherever your Flux controllers run) has outbound access to your Git provider and container registries.
  • Observability: Monitor Flux controller logs and metrics. For enhanced observability, especially in complex networking scenarios, consider tools like eBPF Observability with Hubble to understand network traffic patterns.

Troubleshooting

  1. Issue: Flux does not commit to Git, or the image tag is not updated.

    Solution:
    Check the logs of the image-automation-controller:

    kubectl logs -f -n flux-system deploy/image-automation-controller
    

    Ensure your ImageUpdateAutomation‘s interval is short enough (e.g., 1m).
    Verify that the update.path in ImageUpdateAutomation correctly points to the directory containing your Deployment manifest.
    Double-check the Kustomize setter comment # {"$imagepolicy": "flux-system:my-nginx-policy"} in your Deployment YAML. Ensure the namespace and policy name are correct.
    Confirm that the ImagePolicy is correctly referencing the ImageRepository and that the policy range matches available image tags.

  2. Issue: ImageRepository is not scanning or finding new images.

    Solution:
    Check the logs of the image-reflector-controller:

    kubectl logs -f -n flux-system deploy/image-reflector-controller
    

    Verify the image field in your ImageRepository manifest is correct and includes the full path if it’s not a public Docker Hub image (e.g., myregistry.com/myorg/myapp).
    If using a private registry, ensure the secretRef in ImageRepository points to a valid kubernetes.io/dockerconfigjson secret with correct credentials.

  3. Issue: Flux is committing to Git, but the cluster deployment is not updating.

    Solution:
    This indicates that the Git synchronization part is working, but the reconciliation of the cluster state is failing.
    Check the logs of the kustomize-controller (or helm-controller if you’re using Helm):

    kubectl logs -f -n flux-system deploy/kustomize-controller
    

    Ensure your Kustomization or HelmRelease resource correctly points to the Git path where the updated Deployment manifest resides.
    Verify that the Flux Source (GitRepository or HelmRepository) is healthy and synchronized.

    kubectl get gitrepository -n flux-system
    

  4. Issue: Flux is trying to update to an incorrect or undesirable image tag.

    Solution:
    Review your ImagePolicy carefully. The semver, alphabetical, or numerical rules might be too broad or misconfigured.
    Adjust the range in your semver policy (e.g., >=1.20.0 <1.21.0 for patch-only updates).
    Confirm the ImageRepository is indeed finding the tags you expect. Sometimes, registry filtering can be unexpected.

  5. Issue: Flux is not pushing changes to Git (permission denied).

    Solution:
    This is usually a Git authentication issue.
    If using SSH, ensure the SSH key configured in your Flux GitRepository Secret has write access to your Git repository.
    If using a Personal Access Token (PAT), ensure the token has repo write permissions and is correctly configured in your GitRepository Secret.
    Check the source-controller logs for Git-related errors:

    kubectl logs -f -n flux-system deploy/source-controller
    

FAQ Section

Q1: What’s the difference between ImageRepository, ImagePolicy, and ImageUpdateAutomation?

A1:

  • ImageRepository: Defines *where* to look for images (your container registry) and *which* image name to track. It discovers all available tags for that image.
  • ImagePolicy: Defines *how

Leave a comment