Stop Manually Editing GitOps Files: ArgoCD Image Updater on Kubernetes

Stop Manually Editing GitOps Files: ArgoCD Image Updater on Kubernetes

posted Originally published at alexypulivelil.medium.com 5 min read

Stop Manually Editing GitOps Files: ArgoCD Image Updater on Kubernetes

Every time a developer pushes new code, someone has to manually update the image tag in the GitOps repo. ArgoCD then deploys it. Sound familiar? Let’s fix that.

The Problem

If you’re running ArgoCD with a GitOps setup, you’ve probably experienced this pain:

  • Developer pushes code
  • CI (Jenkins/Gitlab) builds and pushes a new image to the registry
  • Someone (u/me/dev) manually edits the image tag in the GitOps repo
  • ArgoCD detects the change and deploys it.

What is ArgoCD Image Updater?

ArgoCD Image Updater automatically watches your container registry for new image tags and updates your ArgoCD applications without any manual intervention. When a new image is pushed, it detects the change and triggers a deployment without manual interventions.

Prerequisites

Before we begin, please ensure you have:

  • An EKS cluster running
  • kubectl configured
  • helm installed
  • A container registry (DockerHub, ECR, GitLab Registry etc.)
  • A GitHub/GitLab repo for your GitOps values

Step 1 — Install ArgoCD on EKS

First, create the ArgoCD namespace and install it:

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

If you see a CRD annotation size error, apply the server-side flag:

kubectl apply --server-side -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

Access the ArgoCD UI:

kubectl port-forward svc/argocd-server -n argocd 8080:443

Retrieve the initial admin password by opening another terminal:

kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d && echo

Use this password to log in.

Step 2 — Understanding the Multisource Pattern

In real-world GitOps setups, ArgoCD applications often use multiple sources:

  • Source 1 — The Helm chart from a Helm registry (e.g., Bitnami, your internal registry)
  • Source 2 — The values files from your GitOps repository

This is a clean separation — the chart is versioned independently from your environment configuration.

Here’s what a multisource ArgoCD application looks like:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: nginx-sample
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  sources:
    - repoURL: https://charts.bitnami.com/bitnami
      chart: nginx
      targetRevision: 21.0.5
      helm:
        releaseName: nginx-sample
        valueFiles:
          - $values/values/values.yaml
    - repoURL: https://github.com/AlexyPulivelil/gitops-sample.git
      targetRevision: main
      ref: values
  destination:
    server: https://kubernetes.default.svc
    namespace: nginx-sample
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

The values.yaml file typically looks like this:

replicaCount: 1
service:
  type: ClusterIP
  port: 80
image:
  registry: docker.io
  repository: repository/nginix
  tag: "10.0"
  pullPolicy: Always

We are addressing the issue of manually updating these tags on every deployment.

Step 3 — Why ArgoCD Image Updater Old Version Won’t Work Here

(skip this if having single source.)

I tried 0.12.2 initially

kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/v0.12.2/manifests/install.yaml

And add annotations to your ArgoCD Application:

annotations:
  argocd-image-updater.argoproj.io/image-list: myapp=myregistry/myapp
  argocd-image-updater.argoproj.io/myapp.update-strategy: latest

And if you check logs you could see

level=warning msg="skipping app 'nginx-sample' of type '' 
because it's not of supported source type"

v0.12.2 does not support multisource ArgoCD applications. It uses annotations on the Application resource and relies on detecting the source type, which fails for multisource apps.

NB: If working on single source, this would be sufficient

This limitation is why we need v1.1.1.

Step 4 — Install ArgoCD Image Updater v1.1.1

v1.1.1 introduces a CRD based approach — instead of annotations on the ArgoCD Application, you create a dedicated ImageUpdater custom resource. This completely solves the multisource problem.

kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/v1.1.1/config/install.yaml

Verify the controller is running:

kubectl get pods -n argocd | grep image-updater-controller

Grant the controller permissions to access ArgoCD applications:

kubectl create clusterrolebinding image-updater-binding \
  --clusterrole=cluster-admin \
  --serviceaccount=argocd:argocd-image-updater-controller \
  -n argocd

Note: For production, scope down the permissions using a dedicated ClusterRole instead of cluster-admin.

Step 5 — Create the ImageUpdater CR

This is the key difference in v1.1.1. Instead of annotating your ArgoCD Application, you create a separate ImageUpdater resource:

apiVersion: argocd-image-updater.argoproj.io/v1alpha1
kind: ImageUpdater
metadata:
  name: nginx-sample-updater
  namespace: argocd
spec:
  applicationRefs:
    - namePattern: "nginx-sample"
      images:
        - alias: nginx
          imageName: your-registry/nginx-sample-image
          commonUpdateSettings:
            updateStrategy: newest-build
            forceUpdate: true
          manifestTargets:
            helm:
              tag: image.tag
              name: image.repository

What each field means:

namePattern Which ArgoCD application to watch

imageName Which image to monitor in the registry

updateStrategy How to pick the new tag (newest-build, semver, alphabetical)

forceUpdate Update even if image isn't directly referenced in app status

manifestTargets.helm.tag Which Helm value holds the image tag

manifestTargets.helm.name Which Helm value holds the image repository

Apply it:

kubectl apply -f imageupdater-cr.yaml

NB: The apply order should be

# First the ImageUpdater CR
kubectl apply -f argocd/image-updater-cr.yaml
# Then the ArgoCD Application
kubectl apply -f argocd/application.yaml

Now push a new image tag:

docker tag your-dockerhub/nginx:1.0 your-dockerhub/nginx:2.0
docker push your-dockerhub/nginx:2.0

Watch the magic happen in the logs♂️

kubectl logs -f deployment/argocd-image-updater-controller -n argocd

You’ll see

msg="Setting new image to your-dockerhub/nginx:2.0"
msg="Successfully updated image to your-dockerhub/nginx:2.0"
msg="Successfully updated application spec for nginx-sample"
msg="images_updated=1"

captionless image

Zero manual editing. Fully automated.

Key Features

Multiple Update Strategies:

newest-build Always pick the most recently pushed tag

semver Follow semantic versioning constraints e.g. ~1.29

alphabetical Pick the last tag alphabetically

digest Track by image digest

One CR Per Application:

Each ArgoCD Application gets its own ImageUpdater CR. This gives you fine-grained control — each service can have a different update strategy, different registry credentials, and different tag filters.

Write-back Methods:

argocd (default) Updates ArgoCD application parameters directly via API

git Commits the tag change back to your GitOps repo

For true GitOps, use the git write-back method so every deployment change is tracked in Git.

Limitations

Being transparent about limitations is important before adopting any tool in production:

1. One CR per Application: Every ArgoCD Application needs its own ImageUpdater CR. In large setups with many services these requirements can become a lot of CRs to manage.

2. argocd write-back overrides values.yaml: When using the default argocd write-back method, Image Updater sets a parameter override directly on the ArgoCD Application. This takes priority over your values.yaml file. If you manually edit values.yaml, it won't take effect. Use git write-back to avoid this.

3. Registry rate limiting: Image Updater polls the registry every 2 minutes by default. On DockerHub's free tier this can hit rate limits, especially in teams with many services. Configure credentials or use webhooks for instant updates.

4. newest-build picks by push timestamp If you push tags out of order (e.g., push 9.0 after 10.0), Image Updater will pick 9.0 because they were pushed more recently. Use semver strategy to avoid this.

5. No conflict resolution between multiple CRs If two ImageUpdater CRs accidentally target the same ArgoCD Application, they will continuously overwrite each other, causing the application to flip between versions. ensure that only one CR targets each application.

1 Comment

0 votes

More Posts

What Is an Availability Zone Explained Simply

Ijay - Feb 12

Why most people quit AWS

Ijay - Feb 3

AWS Account Locked! How One IAM Mistake Cost Me

Ijay - Mar 18

10 Proven Ways to Cut Your AWS Bill

rogo032 - Jan 16

Can a Non-Technical Person Understand AWS

Ijay - Apr 16
chevron_left

Related Jobs

View all jobs →

Commenters (This Week)

1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!