DEV Community

Alexy Pulivelil
Alexy Pulivelil

Posted on

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Access the ArgoCD UI:

kubectl port-forward svc/argocd-server -n argocd 8080:443
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Verify the controller is running:

kubectl get pods -n argocd | grep image-updater-controller
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

What each field means:

namePatternWhich ArgoCD application to watch

imageNameWhich image to monitor in the registry

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

forceUpdateUpdate even if image isn't directly referenced in app status

manifestTargets.helm.tagWhich Helm value holds the image tag

manifestTargets.helm.nameWhich Helm value holds the image repository

Apply it:

kubectl apply -f imageupdater-cr.yaml
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Watch the magic happen in the logs🧙‍♂️

kubectl logs -f deployment/argocd-image-updater-controller -n argocd
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode


captionless image

Zero manual editing. Fully automated.

Key Features

Multiple Update Strategies:

newest-buildAlways pick the most recently pushed tag

semverFollow semantic versioning constraints e.g. ~1.29

alphabeticalPick the last tag alphabetically

digestTrack 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

gitCommits 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.

Top comments (0)