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
-
kubectlconfigured -
helminstalled - 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:
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
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"
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)