ArgoCD Image Updater#
ArgoCD Image Updater watches container registries for new image tags and automatically updates ArgoCD Applications to use them. In a standard GitOps workflow, updating an image tag requires a Git commit that changes the tag in a values file or manifest. Image Updater automates that step.
The Problem It Solves#
Standard GitOps image update flow:
CI builds image → pushes myapp:v1.2.3 to registry
→ Developer (or CI) commits "update image tag to v1.2.3" to Git
→ ArgoCD detects Git change
→ ArgoCD syncs new tag to clusterThat middle step – committing the tag update – is friction. CI pipelines need Git write access, commit messages are noise (“bump image to v1.2.4”, “bump image to v1.2.5”), and the delay between image push and deployment depends on how fast the commit pipeline runs.
With Image Updater:
CI builds image → pushes myapp:v1.2.3 to registry
→ Image Updater detects new tag in registry
→ Updates ArgoCD Application (or writes back to Git)
→ ArgoCD syncs new tag to clusterInstallation#
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd-image-updater argo/argocd-image-updater \
--namespace argocdOr with plain manifests:
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/stable/manifests/install.yamlImage Updater runs as a separate Deployment in the ArgoCD namespace. It talks to the ArgoCD API server to read and update Application resources.
Configuring an Application for Image Updates#
Image Updater is configured entirely through annotations on ArgoCD Application resources. No changes to the Application spec itself are needed.
Basic Example#
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
annotations:
argocd-image-updater.argoproj.io/image-list: myapp=myorg/my-app
argocd-image-updater.argoproj.io/myapp.update-strategy: semver
spec:
source:
repoURL: https://github.com/myorg/gitops-config.git
targetRevision: main
path: apps/my-app
helm:
parameters:
- name: image.tag
value: "1.0.0"
# ... rest of specThe image-list annotation maps an alias (myapp) to a container image (myorg/my-app). The update strategy (semver) tells Image Updater how to pick the latest tag.
Annotation Reference#
| Annotation | Purpose | Example |
|---|---|---|
image-list |
Images to track (alias=image) | myapp=myorg/my-app |
<alias>.update-strategy |
How to select new tags | semver, latest, digest, name |
<alias>.allow-tags |
Regex filter for allowed tags | regexp:^v[0-9]+\.[0-9]+\.[0-9]+$ |
<alias>.ignore-tags |
Regex filter for ignored tags | regexp:.*-rc.* |
<alias>.helm.image-name |
Helm parameter for image name | image.repository |
<alias>.helm.image-tag |
Helm parameter for image tag | image.tag |
<alias>.helm.image-spec |
Helm parameter for full image spec | image.name (for repo:tag format) |
<alias>.platforms |
Filter by platform/arch | linux/amd64,linux/arm64 |
Update Strategies#
semver#
Selects the highest tag that matches semantic versioning. This is the most common strategy for production.
annotations:
argocd-image-updater.argoproj.io/image-list: myapp=myorg/my-app
argocd-image-updater.argoproj.io/myapp.update-strategy: semver
argocd-image-updater.argoproj.io/myapp.allow-tags: "regexp:^[0-9]+\\.[0-9]+\\.[0-9]+$"With tags 1.0.0, 1.1.0, 1.2.0-rc1, 1.2.0, and 2.0.0 in the registry, and the allow-tags filter excluding prerelease suffixes, Image Updater selects 2.0.0.
Constrain to a major or minor version:
annotations:
argocd-image-updater.argoproj.io/myapp.update-strategy: semver
argocd-image-updater.argoproj.io/myapp.allow-tags: "regexp:^1\\.2\\.[0-9]+$"This only updates within the 1.2.x range.
latest#
Selects the most recently pushed tag, regardless of tag name. Useful for development environments tracking a main branch:
annotations:
argocd-image-updater.argoproj.io/myapp.update-strategy: latest
argocd-image-updater.argoproj.io/myapp.allow-tags: "regexp:^main-[a-f0-9]{7}$"This picks the most recently pushed tag matching main-<short-sha>.
digest#
Tracks a mutable tag (like latest or main) by its digest. When the digest changes, Image Updater updates the Application:
annotations:
argocd-image-updater.argoproj.io/image-list: myapp=myorg/my-app:main
argocd-image-updater.argoproj.io/myapp.update-strategy: digestThe Application image reference changes from myorg/my-app:main to myorg/my-app@sha256:abc123..., pinning to the exact image content.
name#
Selects the tag with the highest lexicographic sort. Useful for date-based tags like 20260222 or build-1234:
annotations:
argocd-image-updater.argoproj.io/myapp.update-strategy: name
argocd-image-updater.argoproj.io/myapp.allow-tags: "regexp:^[0-9]{8}$"Registry Authentication#
Image Updater needs read access to the container registry to list tags and check digests.
Docker Hub#
kubectl -n argocd create secret docker-registry dockerhub-creds \
--docker-server=https://index.docker.io/v1/ \
--docker-username=myuser \
--docker-password=mytokenannotations:
argocd-image-updater.argoproj.io/myapp.pull-secret: pullsecret:argocd/dockerhub-credsAmazon ECR#
ECR tokens expire every 12 hours. Use the ECR credential helper or a CronJob that refreshes the token:
# Image Updater config in argocd-image-updater-config ConfigMap
data:
registries.conf: |
registries:
- name: ECR
prefix: 123456789012.dkr.ecr.us-east-1.amazonaws.com
api_url: https://123456789012.dkr.ecr.us-east-1.amazonaws.com
credentials: ext:/scripts/ecr-login.sh
credsexpire: 10hOr use IRSA (IAM Roles for Service Accounts) to give the Image Updater pod ECR access directly.
GitHub Container Registry (ghcr.io)#
kubectl -n argocd create secret docker-registry ghcr-creds \
--docker-server=ghcr.io \
--docker-username=myuser \
--docker-password=ghp_xxxxxxxxxxxxWrite-Back Methods#
When Image Updater finds a new image, it needs to update something. There are two approaches.
ArgoCD API (Default)#
Image Updater overrides the image parameter directly on the ArgoCD Application resource using parameter overrides. No Git commit is created. The Application shows the updated image in ArgoCD but Git still has the old tag.
annotations:
argocd-image-updater.argoproj.io/write-back-method: argocdPros: Instant, no Git noise. Cons: Git is no longer the single source of truth for the running image tag. If the Application is re-synced from Git, the tag reverts.
Git Write-Back#
Image Updater commits the updated tag to Git. ArgoCD then syncs the Git change like any other commit.
annotations:
argocd-image-updater.argoproj.io/write-back-method: git
argocd-image-updater.argoproj.io/write-back-target: helmvalues:values.yaml
argocd-image-updater.argoproj.io/git-branch: mainFor Kustomize applications:
annotations:
argocd-image-updater.argoproj.io/write-back-method: git
argocd-image-updater.argoproj.io/write-back-target: kustomizationImage Updater needs Git write access. Configure credentials:
annotations:
argocd-image-updater.argoproj.io/write-back-method: git:secret:argocd/git-credsWhere git-creds is a Secret containing the Git credentials.
Git write-back maintains the GitOps principle of Git as the single source of truth. The tradeoff is commit noise and a slight delay while the Git commit propagates.
Write-Back to a Different Branch#
Write image updates to a separate branch to keep the main branch clean of automated commits:
annotations:
argocd-image-updater.argoproj.io/write-back-method: git
argocd-image-updater.argoproj.io/git-branch: image-updatesThe Application’s targetRevision must point to the image-updates branch for the changes to take effect.
Multiple Images#
Track multiple container images in a single Application:
annotations:
argocd-image-updater.argoproj.io/image-list: frontend=myorg/frontend, backend=myorg/backend, worker=myorg/worker
argocd-image-updater.argoproj.io/frontend.update-strategy: semver
argocd-image-updater.argoproj.io/frontend.helm.image-tag: frontend.image.tag
argocd-image-updater.argoproj.io/backend.update-strategy: semver
argocd-image-updater.argoproj.io/backend.helm.image-tag: backend.image.tag
argocd-image-updater.argoproj.io/worker.update-strategy: latest
argocd-image-updater.argoproj.io/worker.helm.image-tag: worker.image.tag
argocd-image-updater.argoproj.io/worker.allow-tags: "regexp:^main-[a-f0-9]{7}$"Each image can have its own update strategy, tag filters, and Helm parameter mapping.
Checking Image Updater Status#
# View Image Updater logs
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-image-updater --tail=100
# Check which images are being tracked
kubectl get applications -n argocd -o json | jq '.items[] | select(.metadata.annotations["argocd-image-updater.argoproj.io/image-list"] != null) | {name: .metadata.name, images: .metadata.annotations["argocd-image-updater.argoproj.io/image-list"]}'
# Force an immediate check
kubectl rollout restart deployment/argocd-image-updater -n argocdThe default check interval is 2 minutes. Configure it in the Image Updater deployment:
env:
- name: IMAGE_UPDATER_INTERVAL
value: "5m"Common Mistakes#
- Using the
argocdwrite-back method in production. If the Application is ever hard-refreshed or recreated from Git, the image tag reverts to whatever is in Git. Usegitwrite-back for production to keep Git as the source of truth. - Not filtering tags with
allow-tags. Without filters, Image Updater considers every tag in the registry, including test builds, debug images, and broken releases. Always restrict to a known tag format. - Forgetting that ECR tokens expire. Static Docker credentials work for Docker Hub and ghcr.io. ECR requires a credential refresh mechanism. Image pulls silently fail after 12 hours without rotation.
- Setting the check interval too low for large registries. Listing tags on a registry with thousands of images every 30 seconds generates significant registry API traffic. Start at 2-5 minutes.
- Not accounting for multi-arch images. If you build for both amd64 and arm64, ensure the tag exists for all required platforms before Image Updater picks it up. Use the
platformsannotation to filter.