Skip to main content

[Ep.17] Closing the gap between CI and CD with the Argo CD Image Updater

Argo CD is very good at one thing: making sure that whatever is stored in Git is what is running on the cluster. It compares both things and updates the cluster accordingly. It is not good at noticing that a CI pipeline has just published a new container image (into the registry). That is the gap between Continuous Integration and Continuous Delivery, and most of us have closed it with a small piece of glue: a pipeline task that clones the configuration repository, patches a tag and pushes the change. I did exactly that in Step 8 of the Secure Supply Chain series with a Tekton task.

With OpenShift GitOps 1.21 the Argo CD Image Updater became generally available. This controller aims to close the gap between CI and CD.

Instead of a hand-written pipeline task, you describe what you would like to do in an ImageUpdater custom resource: which applications to watch, which images to track, which versions are acceptable and where the result should be written.

I tried to create a full walkthrough with this article. We will build a tiny container image, deploy it with Argo CD, let the Image Updater track it, and then push a new version and watch what happens.

What the Image Updater does

The Image Updater is a controller running next to Argo CD. It runs a reconciliation loop that watches the Argo CD applications you selected and queries the container registries for newer tags of the images you configured. When it finds a version that satisfies your constraints, it instructs Argo CD to use the new image.

There are two properties that should be considered before we start.

  1. The Image Updater does not deploy anything. It only changes the desired state, either the Application resource or a file in Git. Whether that change is rolled out depends on the sync policy of your application. With an automated sync policy the new image goes live. Without it, the application turns OutOfSync and waits for manual input.

  2. The Image Updater needs a parameterised manifest, such as Kustomize or Helm. Plain YAML with a hard-coded image in a Deployment is not supported, because there is no parameter for the controller to override.

The supported registries that can be observed for changes, include Docker Hub, Red Hat Quay, GitHub Container Registry, GitLab Container Registry, Google Container Registry, Azure Container Registry, JFrog Artifactory and anything else that implements the Docker Registry v2 API.

Why not just use a pipeline task?

A pipeline task that patches a tag and pushes the change works. I used it myself and it is fine for a single pipeline. But as the number of applications and environments grows, the approach has a few rough edges:

  • The CI pipeline needs write access to the configuration repository. That is a privilege most pipelines should not have. A compromised build could push any image tag it wants. With the Image Updater, only the controller writes to the configuration repository and the CI pipeline only needs push access to the registry.

  • Every pipeline has to carry the same glue code. Cloning a second repository, running kustomize edit set image or yq, committing and pushing: that logic lives in every pipeline, often copy-pasted, and it drifts over time. The Image Updater replaces all of it with a single custom resource.

  • The pipeline only runs when you trigger it. If somebody pushes an image outside of the pipeline, nothing happens. The Image Updater polls the registry independently, so it catches every new tag that matches the constraint, no matter who pushed it.

  • Version constraints are built in. A pipeline task blindly writes the tag it just built. The Image Updater evaluates semantic version ranges, tag filters and ignore lists before it acts. You define what is acceptable once and the controller enforces it.

  • Pull request support comes for free. Opening a pull request from a pipeline task means scripting the GitHub or GitLab API. The Image Updater has it built in.

In short: the pipeline task is imperative glue that couples CI to the configuration repository. The Image Updater is a declarative controller that removes that coupling.

A word about the setup

Everything below was done on a single OpenShift cluster with the default openshift-gitops Argo CD instance. You need:

  • OpenShift Container Platform with the Red Hat OpenShift GitOps Operator 1.21 or later. The ImageUpdater custom resource does not exist in earlier versions.

  • cluster-admin privileges, or at least permission to modify the ArgoCD custom resource.

  • A Git repository for the configuration. I use https://github.com/tjungbauer/argocd-image-updater-demo.git in this article. Replace it with your own.

  • A registry account you can push to. I use Quay.io, because you can create a public repository in a few clicks and push a new tag whenever you want to trigger an update.

  • podman (or docker) on your workstation.

Do not run OpenShift GitOps 1.21.0. Several issues relevant to this article, including a goroutine leak in the Image Updater, were fixed in 1.21.1. Check your version with oc get csv -n openshift-gitops-operator | grep gitops.

Building a test image

The first thing we need to see an update happen is an image whose tags we can control. This can be any image. I have created a simple image for testing purposes: BusyBox with its built-in HTTP server, serving a single page that displays its own version.

Containerfile
FROM docker.io/library/busybox:1.37

ARG APP_VERSION=0.0.0 (1)

RUN mkdir -p /www && \
    printf '<html><body style="font-family:sans-serif">\n<h1>simple-app</h1>\n<p>version: %s</p>\n</body></html>\n' \
      "${APP_VERSION}" > /www/index.html && \
    chmod 0755 /www && chmod 0644 /www/index.html (2)

EXPOSE 8080

USER 1001 (3)

ENTRYPOINT ["/bin/busybox", "httpd", "-f", "-v", "-p", "8080", "-h", "/www"] (4)
1The version is baked in at build time so that you can see in the browser which image is running.
2The files must be readable by any user ID. OpenShift runs the container with a random UID from the namespace range, not with the UID from the image.
3A non-root user. OpenShift would enforce this anyway through the restricted-v2 security context constraint, but declaring it makes the image usable elsewhere as well.
4BusyBox httpd in foreground mode on port 8080. Ports below 1024 would require root privileges, which you do not get.

Build and push the first version. Replace <tjungbau> with your own namespace. (If you use a different registry, replace the registry name accordingly):

podman build --build-arg APP_VERSION=1.0.0 --platform linux/amd64 -t quay.io/tjungbau/simple-app:1.0.0 .
podman push quay.io/tjungbau/simple-app:1.0.0
Make the Quay repository public. A private repository works too, but then you need a pull secret for the cluster and registry credentials for the Image Updater, which adds two moving parts to the first test. There is a section on private registries further down.

Verify that the image runs locally before you involve a cluster:

podman run --rm -p 8080:8080 quay.io/tjungbau/simple-app:1.0.0
curl -s http://localhost:8080

It should return the HTML page with the version number.

<html><body style="font-family:sans-serif">
<h1>simple-app</h1>
<p>version: 1.0.0</p>
</body></html>

The Kubernetes manifests

The configuration repository https://github.com/tjungbauer/argocd-image-updater-demo.git holds a Kustomize overlay. The layout is deliberately small:

Repository layout
argocd-image-updater-demo/
└── overlays
    └── dev
        ├── deployment.yaml
        ├── service.yaml
        ├── route.yaml
        └── kustomization.yaml
Change the repository URL to your own, if you want to use a different repository.
overlays/dev/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: simple-app
  labels:
    app: simple-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: simple-app
  template:
    metadata:
      labels:
        app: simple-app
    spec:
      containers:
        - name: simple-app
          image: quay.io/tjungbau/simple-app:1.0.0 (1)
          ports:
            - name: http
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:
            requests:
              cpu: 10m
              memory: 16Mi
            limits:
              cpu: 100m
              memory: 64Mi
          securityContext: (2)
            allowPrivilegeEscalation: false
            runAsNonRoot: true
            capabilities:
              drop:
                - ALL
            seccompProfile:
              type: RuntimeDefault
1The tag written here is only the starting point. Kustomize overrides it, and later the Image Updater overrides the Kustomize value.
2This block satisfies the restricted-v2 security context constraint explicitly. OpenShift would inject most of it, but if the namespace enforces the restricted Pod Security Standard, the manifest has to carry it.
overlays/dev/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: simple-app
spec:
  selector:
    app: simple-app
  ports:
    - name: http
      port: 8080
      targetPort: http
overlays/dev/route.yaml
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: simple-app
spec:
  to:
    kind: Service
    name: simple-app
  port:
    targetPort: http
  tls:
    termination: edge
    insecureEdgeTerminationPolicy: Redirect
overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: image-updater-demo
resources:
  - deployment.yaml
  - service.yaml
  - route.yaml
images: (1)
  - name: quay.io/tjungbau/simple-app
    newTag: "1.0.0"
1This is the parameter the Image Updater will modify. Without an images section, Kustomize has nothing to override and the Image Updater has nothing to write to.

Be sure that the above manifests are pushed to your repository.

The Argo CD application

The Argo CD application is a normal one. No annotations, no special fields. As repository, we use the one we just created.

Argo CD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: image-updater-demo
  namespace: openshift-gitops (1)
spec:
  project: default
  source:
    repoURL: https://github.com/tjungbauer/argocd-image-updater-demo.git
    targetRevision: main
    path: overlays/dev
  destination:
    server: https://kubernetes.default.svc
    namespace: image-updater-demo
  syncPolicy:
    automated: (2)
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
1Remember this namespace. The ImageUpdater resource has to live in the same namespace as the applications it references.
2With an automated sync policy an image update is rolled out immediately. Leave it out if you want a human to press the button.

Apply the application and check that it comes up:

oc apply -f application.yaml
oc get application image-updater-demo -n openshift-gitops
oc get pods,route -n image-updater-demo

Open the route in a browser. It should show version: 1.0.0.

Enabling the Argo CD Image Updater

The controller is enabled per Argo CD instance in the ArgoCD custom resource. By default, it is not enabled.

Enable the Image Updater
apiVersion: argoproj.io/v1beta1
kind: ArgoCD
metadata:
  name: openshift-gitops
  namespace: openshift-gitops
spec:
  imageUpdater:
    enabled: true (1)
1Deploys the Image Updater controller for this Argo CD instance.

If you manage the Argo CD instance declaratively, and you should (see Ep.3), add the block to your existing manifest. For a quick test, patch the resource:

oc patch argocd openshift-gitops -n openshift-gitops \
  --type merge \
  -p '{"spec":{"imageUpdater":{"enabled":true}}}'

Wait for the controller to appear. The Operator names the deployment after the Argo CD instance, so for the default instance it is openshift-gitops-argocd-image-updater-controller.

oc get deployment -n openshift-gitops | grep image-updater
Example output
openshift-gitops-argocd-image-updater-controller   1/1     1     1     2m
If you use a differently named Argo CD instance, the deployment name changes with it. The commands in this article use the default name. Store it in a variable if you work on a custom instance.

Keep the log open in a second terminal for the rest of this article:

oc logs -n openshift-gitops \
  deployment/openshift-gitops-argocd-image-updater-controller -f

Which namespaces are watched

By default the controller only looks at the namespace it was installed in. As soon as you use applications in any namespace, you have to tell it where else to look. This is done with environment variables in the same block:

Watching several namespaces and tuning the controller
apiVersion: argoproj.io/v1beta1
kind: ArgoCD
metadata:
  name: openshift-gitops
  namespace: openshift-gitops
spec:
  imageUpdater:
    enabled: true
    env:
      - name: IMAGE_UPDATER_LOGLEVEL (1)
        value: info
      - name: IMAGE_UPDATER_WATCH_NAMESPACES (2)
        value: "openshift-gitops,image-updater-demo"
1Log level of the controller. Valid values are debug, info, warn and error. Set it to debug while you build your first ImageUpdater resources. It tells you which tags were considered and why they were rejected.
2Comma-separated list of namespaces watched for ImageUpdater resources and for Argo CD applications.

The first ImageUpdater resource

The ImageUpdater custom resource answers three questions:

  1. which applications

  2. which images

  3. where does the result go

imageupdater.yaml
apiVersion: argocd-image-updater.argoproj.io/v1alpha1
kind: ImageUpdater
metadata:
  name: image-updater-demo
  namespace: openshift-gitops (1)
spec:
  applicationRefs:
    - namePattern: "image-updater-demo" (2)
      images:
        - alias: "simple-app" (3)
          imageName: "quay.io/tjungbau/simple-app:~1.0" (4)
1The controller uses metadata.namespace to decide where to search for applications. A resource in openshift-gitops will never find an application in image-updater-demo.
2Glob pattern matching the application name.
3A name for this image inside the resource. You need it when you map Helm parameters later.
4The image, including a version constraint. ~1.0 allows patch releases inside 1.0, so 1.0.1 and 1.0.7 are accepted while 1.1.0 is not.

Apply it and check the status:

oc apply -f imageupdater.yaml
oc get imageupdater image-updater-demo -n openshift-gitops -o yaml

If this returns 1, the selector works. If it returns 0 or nothing, the resource is in the wrong namespace or the name pattern does not match.

Always set a version constraint. Without one, the Image Updater may move to any newer version it finds, including a major release. In 1.21 the field spec.namespace was removed from this API. A resource that still contains it fails validation.
A second rule that is easy to break in a large repository: never let two ImageUpdater resources match the same application. Both will update it and overwrite each other, and the image version will keep flipping back and forth.

Triggering the first update

Now build and push a new patch version:

podman build --build-arg APP_VERSION=1.0.1 --platform linux/amd64 -t quay.io/tjungbau/simple-app:1.0.1 .
podman push quay.io/tjungbau/simple-app:1.0.1

The controller polls the registry, so the update appears after the reconciliation interval. Watch the log, then confirm the result on the Application resource:

oc get application image-updater-demo -n openshift-gitops \
  -o jsonpath='{.spec.source.kustomize.images}{"\n"}'
Example output
["quay.io/tjungbau/simple-app:1.0.1"]

This is the default write-back method at work: the controller wrote a Kustomize parameter override into the Application resource on the cluster. Because the application has an automated sync policy, Argo CD rolls it out. Reload the route and the page shows version: 1.0.1.

The update history in the status tells you what happened and when:

oc get imageupdater image-updater-demo -n openshift-gitops -o jsonpath='{.status.recentUpdates}{"\n"}'
Example output
[{"alias":"simple-app","applicationsUpdated":1,"image":"quay.io/tjungbau/simple-app","message":"Updated from 1.0.0 to 1.0.1.","newVersion":"1.0.1","updatedAt":"2026-09-15T15:36:07Z"}]

Now look at the repository. Nothing changed there. The kustomization.yaml still says 1.0.0, and the cluster runs 1.0.1. Argo CD simply overrides the value with the new one. That is the reason the next section exists.

Write-back methods

Method 1: Argo CD API, the default

The controller writes the new image into the Application resource, which is what you just saw. No configuration is needed. If you omit writeBackConfig, this is what you get.

It is quick and it is simple. It also has a failure mode that costs people an afternoon: if the Application itself is managed in Git, for example through an App-of-Apps pattern (see Ep.4), the next sync from Git overwrites the parameter override. The cluster falls back to the old image and the reason is not obvious.

Use this method for applications created through the UI or the CLI, and for experiments like the one above.

Do not treat this method as a production-ready solution.

Method 2: Git write-back

The Git method fits the idea that the repository is the single source of truth. The controller clones the repository, checks out the branch, writes the change and pushes it.

Here a secret must be created that holds credentials with write access to the configuration repository. For HTTPS the secret needs the keys username and password, and the password should be a personal access token:

oc create secret generic git-creds -n openshift-gitops \
  --from-literal=username=my-bot-user \
  --from-literal=password=<personal_access_token>

Then extend the ImageUpdater:

Git write-back
apiVersion: argocd-image-updater.argoproj.io/v1alpha1
kind: ImageUpdater
metadata:
  name: my-app-updater
  namespace: openshift-gitops
spec:
  applicationRefs:
    - namePattern: "my-app-dev"
      images:
        - alias: "simple-app"
          imageName: "quay.io/tjungbau/simple-app:~1.0"
  writeBackConfig:
    method: "git:secret:openshift-gitops/git-creds" (1)
    gitConfig:
      repository: "https://github.com/tjungbauer/argocd-image-updater-demo.git" (2)
      branch: "main" (3)
1Use the Git method with the credentials from the referenced secret. Plain method: "git" reuses the credentials Argo CD already has for the repository, which are often read-only.
2The repository that receives the commit.
3The target branch.
The secret has to live in the same namespace as the ImageUpdater resource (here: openshift-gitops). Cross-namespace references are rejected, so that a team cannot read a secret it does not own.

Push version 1.0.2 and watch the repository:

podman build --build-arg APP_VERSION=1.0.2 --platform linux/amd64 -t quay.io/tjungbau/simple-app:1.0.2 .
podman push quay.io/tjungbau/simple-app:1.0.2

By default the controller does not touch your kustomization.yaml. It creates or updates a file called .argocd-source-image-updater-demo.yaml in the application path. That file contains a parameter override which Argo CD merges on top of the rendered manifests. Your own files stay untouched, which is safe, but it also means the repository now contains two statements about the image version: kustomization.yaml says 1.0.0 and the generated file says 1.0.2.

If you prefer the commit to change the source of truth itself, set a write-back target:

Kustomize write-back
  writeBackConfig:
    method: "git:secret:openshift-gitops/git-creds"
    gitConfig:
      repository: "https://github.com/mycompany/my-app-config.git"
      branch: "main"
      writeBackTarget: "kustomization" (1)
1The change is committed as a kustomize edit set image operation, so kustomization.yaml carries the new tag.

For Helm applications the equivalent target is a values file:

Helm write-back
  writeBackConfig:
    method: "git:secret:openshift-gitops/git-creds"
    gitConfig:
      branch: "main"
      writeBackTarget: "helmvalues:/helm/config/values.yaml"
after switching from the API method to a Git method, remove the leftover override from the Application resource once, otherwise the old value stays in spec.source.kustomize.images and keeps winning: oc patch application my-app-dev -n openshift-gitops --type json -p '[{"op":"remove","path":"/spec/source/kustomize/images"}]'

Method 3: Pull request or merge request

If your main branch is protected, which makes sense, the controller cannot push to it. The pull request method extends the Git method. The commit goes to a generated branch named image-updater-<namespace>-<appName>-<sha256>, and a pull or merge request is opened against the base branch. If a request for the same branch pair already exists, no duplicate is created.

GitHub pull request
  writeBackConfig:
    method: "git:secret:openshift-gitops/git-creds"
    gitConfig:
      repository: "https://github.com/mycompany/my-app-config.git"
      branch: "main"
      pullRequest:
        github: {} (1)
1For GitLab, use gitlab: {} instead.

Push version 1.0.3 and watch the repository:

podman build --build-arg APP_VERSION=1.0.3 --platform linux/amd64 -t quay.io/tjungbau/simple-app:1.0.3 .
podman push quay.io/tjungbau/simple-app:1.0.3

And once the Controller has created the pull request, you can see it in the GitHub UI:

Pull Request

This pull request can now be reviewed and merged (or rejected).

Creating a pull request requires credentials with a bearer token, so a personal access token or GitHub App credentials. SSH keys do not work here, because the controller has to call the SCM API over HTTP and an SSH key provides no token for that.

Choosing a method

MethodUse it whenConsequence

Argo CD API

Playgrounds, demos, applications not managed in Git

A sync from Git reverts the update

Git, direct push

Development and test environments

A bot account with write access to the configuration repository

Git, pull request

Production and protected branches

Somebody has to merge the request, otherwise nothing is deployed

Update strategies

A tag like v1.0.0-3f2a1b is not a semantic version, and latest never changes its name. Four strategies are available, configured with commonUpdateSettings.updateStrategy:

StrategyDescription

semver (default)

Updates to the highest version matching the semantic version constraint. The right choice when the pipeline produces release tags, as in the example above.

newest-build

Updates to the tag with the most recent creation timestamp, regardless of version numbering. This is the strategy for pipelines that tag with a build ID or a commit SHA.

alphabetical

Updates to the last tag when sorted alphabetically. Useful for date-based tags such as 2026-09-06.

digest

Tracks a mutable tag such as latest by its SHA256 digest and updates when the digest changes.

In practice a strategy is combined with a tag filter, because registries contain plenty of tags you usually do not want to deploy:

newest-build with a tag filter
spec:
  applicationRefs:
    - namePattern: "image-updater-demo"
      images:
        - alias: "simple-app"
          imageName: "quay.io/tjungbau/simple-app"
          commonUpdateSettings:
            updateStrategy: "newest-build" (1)
            allowTags: "regexp:^build-[0-9]+$" (2)
1Take the most recently built image instead of the highest number.
2Only tags matching this regular expression are considered.

Two parameters control the filtering:

allowTags

A match function applied to every tag. Supports regexp:<expression> and any, which is the default.

ignoreTags

A comma-separated list of glob patterns that are excluded.

Allowing release tags and ignoring release candidates
          commonUpdateSettings:
            allowTags: "regexp:^[0-9]+\\.[0-9]+\\.[0-9]+$"
            ignoreTags: "*-rc*"
If a filter does not behave as expected, set IMAGE_UPDATER_LOGLEVEL to debug. The log shows which tags were fetched and which ones survived the filter. That is faster than guessing at the regular expression.

To try the newest-build strategy with the demo image, push a few tags in a row:

for i in 1 2 3; do
  podman build --build-arg APP_VERSION="build-${i}" -t quay.io/tjungbau/simple-app:build-${i} .
  podman push quay.io/tjungbau/simple-app:build-${i}
done

Selecting applications

Name patterns are convenient, but in a fleet generated by an ApplicationSet, labels scale better:

Selecting applications by label
spec:
  applicationRefs:
    - labelSelectors:
        matchLabels:
          tier: "frontend"
        matchExpressions:
          - key: env
            operator: In
            values:
              - staging
              - production
      images:
        - alias: "simple-app"
          imageName: "quay.io/<your_quay_account>/simple-app:~1.0"

Name pattern and label selector can be combined, in which case both have to match.

There is a third variant meant for ApplicationSets. If every generated application should carry its own image configuration, set useAnnotations: true. The controller then reads the configuration from the argocd-image-updater.argoproj.io/image-list annotation of each application instead of from the resource:

Reading the image configuration from the applications
spec:
  applicationRefs:
    - namePattern: "generated-app-*"
      useAnnotations: true (1)
1With this enabled, an images block in the resource is ignored for this applicationRef. Only namePattern and labelSelectors remain effective.
Combining useAnnotations: true with namePattern: "*" and no label selector means every application in the namespace is processed. That works, but it costs performance on a large instance.

Private registries

Public registries work without configuration. For a private one you have two options.

Per image, reference a pull secret in the ImageUpdater:

Using a pull secret for one image
spec:
  applicationRefs:
    - namePattern: "my-app-dev"
      images:
        - alias: "simple-app"
          imageName: "registry.example.com/mycompany/simple-app:~1.0"
          pullSecret: "pullsecret:openshift-gitops/myregistry-pull-secret" (1)
1Uses a Docker-style pull secret with a .dockerconfigjson key.

The well-known registries are supported out of the box.

For a registry that is not known out of the box, describe it once in the argocd-image-updater-config config map in the namespace of the Argo CD instance:

Registering a custom registry
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-image-updater-config
  namespace: openshift-gitops
data:
  registries.conf: |
    registries:
    - name: My Private Registry
      prefix: myregistry.example.com (1)
      api_url: https://myregistry.example.com (2)
      credentials: secret:openshift-gitops/registry-creds#creds (3)
      default: false (4)
1The prefix that image names start with.
2The registry API endpoint.
3Credentials for querying the registry API.
4Whether this registry is used for images without a registry prefix.

Practical considerations

These are the questions that come up in customer workshops.

Who is committing to our repository?

A bot account with a personal access token. Treat it like any other privileged identity: a dedicated account, minimal scope, rotated regularly, and no administrator rights. If branch protection requires signed commits, the git command line inside the controller has to sign them, which means providing a GPG or SSH signing key to the pod. Upstream this is configured with git.commit-signing-key and git.commit-signing-method in the argocd-image-updater-config config map. I was not able to test this yet, but it should be considered for production use.

Does this break our Secure Supply Chain?

No, but it changes where the gate sits. Previously the pipeline was the only writer, and it had already verified signatures, scanned the image with ACS and generated an SBOM (see the Secure Supply Chain series). If the Image Updater now picks images directly from the registry, the tag itself becomes the contract. Let the pipeline push a release tag such as 1.2.3 only after all checks passed, and restrict the Image Updater with allowTags: "regexp:^\\.[0-9]\\.[0-9]+$". Everything that failed a check never gets a tag that the Image Updater accepts.

Can we roll back?

With a Git write-back method, a rollback is a git revert. With the API method there is nothing to revert.

Limitations

  • Only Kustomize-rendered and Helm-rendered manifests are supported. A Helm chart has to expose parameters for the image.

  • Image pull secrets have to exist on the cluster where the controller runs.

  • One application has to be targeted by exactly one ImageUpdater resource.

Summary

The Argo CD Image Updater replaces a piece of glue that most of us wrote ourselves, and it does so declaratively. An ImageUpdater resource states which applications are watched, which images are tracked, which versions are acceptable and where the result is persisted. Since OpenShift GitOps 1.21 it is generally available and enabled with one flag in the ArgoCD custom resource.

If you take one thing from this article, let it be the write-back decision. The default API method is the fastest way to a working demo and the fastest way to a confusing incident. For anything that matters, write back to Git: directly on a development branch, and through a pull request wherever the branch is protected. The repository stays the source of truth, a rollback stays a git revert, and the audit trail stays intact.

And as always: If it is not in Git, it does not exist.


Discussion

Previous
Use arrow keys to navigate
Next