ArgoCD Image Updater for Automated Container Deployments
The Annotation First
Here's an ArgoCD Application that auto-deploys when a new image is pushed. Then I'll break it down.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api-service
namespace: argocd
annotations:
argocd-image-updater.argoproj.io/image-list: api=ghcr.io/your-org/api-service
argocd-image-updater.argoproj.io/api.update-strategy: semver
argocd-image-updater.argoproj.io/api.semver-constraint: ">=1.0.0 <2.0.0"
argocd-image-updater.argoproj.io/write-back-method: git
argocd-image-updater.argoproj.io/write-back-target: kustomization
argocd-image-updater.argoproj.io/git-branch: main
spec:
project: default
source:
repoURL: https://github.com/your-org/k8s-manifests
targetRevision: main
path: apps/api-service/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
Push an image tagged v1.5.0 to your registry. Image Updater detects it, commits the update to your Git repo, ArgoCD syncs. No human in the loop.
Why Image Updater
Standard GitOps has a gap. Your CI builds and pushes a new image. Then someone — or some script — has to update the image tag in the manifests repo. That manual step is where deployments stall.
ArgoCD Image Updater closes the gap:
- Watches container registries for new tags matching your constraints
- Updates manifests automatically via Git commit or ArgoCD parameter override
- Respects semantic versioning — no accidental major version bumps
- Maintains GitOps principles — every change is tracked in Git
If it's not automated, it doesn't exist. A deployment pipeline that needs a human to bump an image tag is not a pipeline.
Installation
# Install via Helm
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argocd-image-updater argo/argocd-image-updater \
--namespace argocd \
--set config.registries[0].name=GitHub \
--set config.registries[0].prefix=ghcr.io \
--set config.registries[0].api_url=https://ghcr.io \
--set config.registries[0].credentials=pullsecret:argocd/ghcr-creds \
--set config.registries[0].default=true
Or apply the manifests directly:
kubectl apply -n argocd \
-f https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/stable/manifests/install.yaml
Registry Authentication
Image Updater needs to read tags from your registry:
apiVersion: v1
kind: Secret
metadata:
name: ghcr-creds
namespace: argocd
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: <base64-encoded-docker-config>
# Create the secret from a token
kubectl create secret docker-registry ghcr-creds \
--namespace argocd \
--docker-server=ghcr.io \
--docker-username=your-bot-user \
--docker-password=$GHCR_TOKEN
Use a bot account with read-only access. Never your personal token.
Update Strategies
Semantic Versioning (Recommended)
annotations:
argocd-image-updater.argoproj.io/image-list: app=ghcr.io/org/app
argocd-image-updater.argoproj.io/app.update-strategy: semver
argocd-image-updater.argoproj.io/app.semver-constraint: ">=1.0.0 <2.0.0"
Only picks up patch and minor versions within your constraint. A v2.0.0 tag is ignored. This is the safe default.
Latest Build (By Timestamp)
annotations:
argocd-image-updater.argoproj.io/image-list: app=ghcr.io/org/app
argocd-image-updater.argoproj.io/app.update-strategy: latest
argocd-image-updater.argoproj.io/app.allow-tags: "regexp:^main-[a-f0-9]{7}$"
Picks the most recently built image matching the tag pattern. Good for dev/staging where you want every commit deployed.
Digest-Based
annotations:
argocd-image-updater.argoproj.io/image-list: app=ghcr.io/org/app:stable
argocd-image-updater.argoproj.io/app.update-strategy: digest
Watches a mutable tag and triggers updates when the digest changes. Useful when tags like stable are overwritten.
Write-Back Methods
Git Write-Back (Recommended)
annotations:
argocd-image-updater.argoproj.io/write-back-method: git
argocd-image-updater.argoproj.io/write-back-target: kustomization
argocd-image-updater.argoproj.io/git-branch: main
Image Updater commits a .argocd-source-<app>.yaml file or updates your kustomization.yaml. True GitOps — every image change is a Git commit.
You need a Git write-back secret:
apiVersion: v1
kind: Secret
metadata:
name: git-creds
namespace: argocd
type: Opaque
data:
username: <base64>
password: <base64>
ArgoCD Parameter Override
annotations:
argocd-image-updater.argoproj.io/write-back-method: argocd
Updates the image tag via ArgoCD's parameter override. Faster but not persisted in Git. If ArgoCD restarts, the override is lost. Use Git write-back for production.
Multi-Container Applications
annotations:
argocd-image-updater.argoproj.io/image-list: >
api=ghcr.io/org/api,
worker=ghcr.io/org/worker,
migrator=ghcr.io/org/migrator
argocd-image-updater.argoproj.io/api.update-strategy: semver
argocd-image-updater.argoproj.io/api.semver-constraint: ">=2.0.0 <3.0.0"
argocd-image-updater.argoproj.io/worker.update-strategy: semver
argocd-image-updater.argoproj.io/worker.semver-constraint: ">=1.0.0 <2.0.0"
argocd-image-updater.argoproj.io/migrator.update-strategy: digest
Each image gets its own alias and strategy. The migrator uses digest because it's always tagged latest.
Kustomize Integration
When using Git write-back with Kustomize, Image Updater modifies your kustomization.yaml:
# kustomization.yaml (before)
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
# After Image Updater runs, it adds:
images:
- name: ghcr.io/org/api-service
newTag: v1.5.0
Clean. Auditable. git log shows exactly when each image version was deployed.
Monitoring
Check what Image Updater is doing:
# View logs
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-image-updater
# Check which applications are being tracked
kubectl get applications -n argocd -o json | \
jq '.items[] | select(.metadata.annotations["argocd-image-updater.argoproj.io/image-list"]) | .metadata.name'
Image Updater also exposes Prometheus metrics on port 8081:
# ServiceMonitor for Prometheus
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: argocd-image-updater
spec:
selector:
matchLabels:
app.kubernetes.io/name: argocd-image-updater
endpoints:
- port: metrics
Troubleshooting
Problem: Image Updater doesn't detect new tags.
Fix: Check registry credentials. Run argocd-image-updater test <image> to verify access.
Problem: Git write-back commits fail.
Fix: Ensure the Git credentials secret exists in the argocd namespace and the bot user has write access.
Problem: Wrong image gets selected.
Fix: Tighten your allow-tags regex or semver-constraint. Use argocd-image-updater test <image> --semver-constraint ">=1.0.0" to preview matches.
Filtering Tags with Regex
Real-world registries have noisy tags. Filter them precisely.
annotations:
# Only match tags like: v1.2.3, v1.2.3-rc.1
argocd-image-updater.argoproj.io/app.allow-tags: "regexp:^v[0-9]+\\.[0-9]+\\.[0-9]+(-rc\\.[0-9]+)?$"
# Ignore pre-release tags in production
argocd-image-updater.argoproj.io/app.ignore-tags: "regexp:(-alpha|-beta|-rc)"
Without tag filtering, Image Updater might pick up a dev-abc1234 tag pushed by a feature branch build. Always constrain what it can deploy.
Per-Environment Configuration
Different environments need different update strategies. Here's a pattern that works.
Development: Deploy Every Commit
# dev/api-service.yaml
metadata:
annotations:
argocd-image-updater.argoproj.io/image-list: api=ghcr.io/org/api
argocd-image-updater.argoproj.io/api.update-strategy: latest
argocd-image-updater.argoproj.io/api.allow-tags: "regexp:^main-[a-f0-9]{7}$"
argocd-image-updater.argoproj.io/write-back-method: git
argocd-image-updater.argoproj.io/git-branch: deploy/dev
Every commit to main deploys to dev within minutes. The deploy/dev branch keeps dev changes separate from prod manifests.
Staging: Deploy Release Candidates
# staging/api-service.yaml
metadata:
annotations:
argocd-image-updater.argoproj.io/image-list: api=ghcr.io/org/api
argocd-image-updater.argoproj.io/api.update-strategy: semver
argocd-image-updater.argoproj.io/api.semver-constraint: ">=1.0.0-rc.0"
argocd-image-updater.argoproj.io/api.allow-tags: "regexp:^v[0-9]+\\.[0-9]+\\.[0-9]+-rc"
argocd-image-updater.argoproj.io/write-back-method: git
argocd-image-updater.argoproj.io/git-branch: deploy/staging
Production: Only Stable Releases
# prod/api-service.yaml
metadata:
annotations:
argocd-image-updater.argoproj.io/image-list: api=ghcr.io/org/api
argocd-image-updater.argoproj.io/api.update-strategy: semver
argocd-image-updater.argoproj.io/api.semver-constraint: ">=1.0.0 <2.0.0"
argocd-image-updater.argoproj.io/api.ignore-tags: "regexp:(-rc|-alpha|-beta)"
argocd-image-updater.argoproj.io/write-back-method: git
argocd-image-updater.argoproj.io/git-branch: deploy/prod
Each environment has its own branch for write-back. This gives you a full Git history of what was deployed where and when.
Helm Values Integration
If your applications use Helm charts, Image Updater can update Helm values instead of Kustomize:
metadata:
annotations:
argocd-image-updater.argoproj.io/image-list: api=ghcr.io/org/api
argocd-image-updater.argoproj.io/api.update-strategy: semver
argocd-image-updater.argoproj.io/api.helm.image-name: image.repository
argocd-image-updater.argoproj.io/api.helm.image-tag: image.tag
argocd-image-updater.argoproj.io/write-back-method: git
argocd-image-updater.argoproj.io/write-back-target: helmvalues:values.yaml
Image Updater writes the new tag into your values.yaml:
# values.yaml (after Image Updater update)
image:
repository: ghcr.io/org/api
tag: v1.5.0
This works with any Helm chart structure. Just point the helm.image-name and helm.image-tag annotations to the correct value paths.
Rollback Strategy
Automated deployments need automated rollbacks. When Image Updater deploys a bad version, you need to recover fast.
Git Revert
Since every image update is a Git commit, rollback is a git revert:
# Find the Image Updater commit
git log --oneline --author="argocd-image-updater" -5
# Revert the bad deployment
git revert abc1234
git push origin deploy/prod
ArgoCD syncs the reverted manifest and rolls back the deployment. Full audit trail in Git.
Pin to a Known Good Version
For immediate mitigation, override the image in ArgoCD directly:
argocd app set api-service \
--helm-set image.tag=v1.4.0
This overrides Image Updater temporarily. Fix the root cause, then remove the override and let Image Updater resume.
Alerting on Update Failures
Image Updater can fail silently. Monitor it proactively.
# Prometheus alert for Image Updater failures
groups:
- name: argocd-image-updater
rules:
- alert: ImageUpdaterUpdateFailed
expr: |
increase(argocd_image_updater_images_updated_errors_total[1h]) > 0
for: 5m
labels:
severity: warning
annotations:
summary: "ArgoCD Image Updater failed to update images"
runbook: "https://wiki.internal/runbooks/image-updater-failure"
- alert: ImageUpdaterNotRunning
expr: |
absent(argocd_image_updater_images_checked_total) == 1
for: 10m
labels:
severity: critical
annotations:
summary: "ArgoCD Image Updater is not running"
Check logs regularly to spot issues before they become incidents:
# Show recent update activity
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-image-updater \
--since=1h | grep -E "(updated|error|failed)"
Common Pitfalls
Pitfall 1: Not testing in a non-prod environment first. Image Updater doesn't know if your new image is healthy. Use dev/staging environments with the same setup. If v1.5.0 breaks staging, it never reaches prod.
Pitfall 2: Overly broad tag matching. Without allow-tags or ignore-tags, Image Updater picks up every tag including CI builds and debug images. Always constrain the tag pattern.
Pitfall 3: Missing health checks on deployments. If the new image crashes, Kubernetes rolls back the Deployment only if you have proper readinessProbe and livenessProbe configured. Without health checks, a crashing container keeps restarting indefinitely.
Pitfall 4: Write-back branch conflicts. If multiple Image Updater instances write to the same branch simultaneously, you get merge conflicts. Use separate branches per environment or per application.
Pitfall 5: Forgetting to configure the update interval. Default check interval is 2 minutes, which means high registry API usage. For production, increase to 5-10 minutes to reduce API calls and rate limit risk.
Conclusion
ArgoCD Image Updater removes the last manual step from your GitOps pipeline. CI builds and pushes images. Image Updater detects them, updates manifests in Git, and ArgoCD syncs to the cluster. Use semver constraints for production, latest strategy for dev, and always prefer Git write-back. Filter tags aggressively, separate environments with dedicated branches, and always have a rollback plan. Fully automated, fully auditable, fully GitOps.
Related Articles
CI/CD Engineering Lead
Automation evangelist who believes no deployment should require a human. I write pipelines, break pipelines, and write about both. Code-first, always.
Related Articles
ArgoCD Application Patterns: App of Apps, ApplicationSets, and Beyond
Practical ArgoCD patterns for managing dozens of applications — from App of Apps to ApplicationSets to multi-cluster rollouts. All in code, obviously.
Mozilla SOPS: Encrypted Secrets in Git for GitOps Workflows That Don't Leak
Use Mozilla SOPS to encrypt secrets in Git for secure GitOps workflows. Covers AGE, AWS KMS, and ArgoCD integration with real examples.
Crossplane: Managing Cloud Infrastructure from Kubernetes
How to use Crossplane to provision and manage cloud infrastructure using Kubernetes-native APIs — one control plane to rule them all.