ArgoCD - How to Automate Deployments

ArgoCD - How to Automate Deployments

Disclaimer: This post was originally written for my company’s blog . I’ve translated it and added a personal touch for this space.

In the previous ArgoCD article, we walked through installing ArgoCD and using it to deploy a Helm-based application across multiple environments.

What I deliberately skipped was the natural follow-up question: How can new container images be automatically rolled out without manually triggering syncs or touching the cluster?

That’s exactly what this article is about. We’ll look at common ways to integrate CI/CD pipelines with ArgoCD and then dive deeper into one of the most practical options: the ArgoCD Image Updater, including a step-by-step setup.

Automation Strategies

The goal of our setup is a clean GitOps-style workflow: Our GitLab pipeline builds new container images, pushes them to the GitLab Container Registry, and ArgoCD ensures those images end up running in the cluster — automatically and reliably.

There are three common ways to achieve this.

Git Commit from the Pipeline

The CI/CD pipeline updates the image tag directly in Git (e.g., in the values.yaml file) and commits the change. ArgoCD detects the Git update and deploys it. This method is fully Git-driven and traceable - but it requires Git access and commit rights for the pipeline. This adds a bit of complexity and is also not super desirable from a security perspective.

Trigger an ArgoCD Sync from the Pipeline

Here, the new image tag is already present in Git (e.g., via dynamic tags like latest, dev-*, etc.). After pushing the image, the pipeline explicitly triggers a sync of the ArgoCD Application via API or CLI. While this is easy and quick to implement, it comes with a downside: The deployment happens without a corresponding Git change. That breaks the core GitOps idea of Git being the single source of truth.

Use the ArgoCD Image Updater

The ArgoCD Image Updater is a controller running inside the cluster that regularly checks container registries for new tags and reacts accordingly.

If it finds a new tag, it can update the Git repository accordingly. These Git changes are picked up by ArgoCD and deployed. Alternatively, it can also trigger deployments without modifying Git. Either way, this setup requires some additional configuration - but with the benefit of keeping the pipeline simple and bringing the deployment logic back to ArgoCD.

Since this article focuses on ArgoCD itself, let’s take a closer look at its native solution - the Image Updater.

Prerequisites

If you want to follow along, you’ll need the setup from the previous ArgoCD article, including:

  • a running ArgoCD instance
  • the Helm charts in GitLab
  • a CI pipeline and images pushed to the GitLab Container Registry

To keep things simple for now, we’ll work with plain Application YAMLs for a frontend and backend - and skip ApplicationSets and AppProjects.

Updating the Existing Configurations

Before installing the Image Updater, we need to prepare our existing setup:

values.yaml

In each values.yaml, replace the image tag with a placeholder like this:

repository: <Registry link>
tag: "abc123"

The Image Updater will later overwrite this value.

In our Application files, we’ll add annotations so the Image Updater knows what to track and how to update:

frontend.yaml

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: frontend-dev
  namespace: argocd
  annotations:
    argocd-image-updater.argoproj.io/image-list: frontend=<Registry Link>
    argocd-image-updater.argoproj.io/frontend.update-strategy: newest-build
    argocd-image-updater.argoproj.io/frontend.helm.image-tag: image.tag
    argocd-image-updater.argoproj.io/frontend.helm.image-name: image.repository
spec:
  project: default
  source:
    repoURL: <Repository Link>
    targetRevision: main
    path: <Path to Helm Chart>
    helm:
      valueFiles:
        - values/dev.yaml
  destination:
    server: https://kubernetes.default.svc
    namespace: dev
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

backend.yaml follows the same pattern.

We can now apply the updated Applications with:

kubectl apply -f frontend.yaml
kubectl apply -f backend.yaml

Creating a Registry Secret

To allow the Image Updater to read from the GitLab Container Registry, it needs appropriate credentials. We’ll use a GitLab Deploy Token with Read Registry permissions.

We create the token under Settings → Repository → Deploy Tokens, then prepare the credentials.

Base64-encode the credentials:

echo -n "<Deploy Token Name>:<Deploy Token Password>" | base64

Create the .dockerconfigjson file:

{
  "auths": {
    "<Registry Link>": {
      "username": "<Deploy Token Name>",
      "password": "<Deploy Token Password>",
      "email": "REGISTRY_EMAIL",
      "auth": "Base64-Credentials from above"
    }
  }
}

Encode the file:

cat .dockerconfigjson | base64

Create the Kubernetes Secret:

apiVersion: v1
kind: Secret
type: kubernetes.io/dockerconfigjson
metadata:
  name: registry-credentials
  namespace: argocd
data:
  .dockerconfigjson: <base64 output from encoding the file>

Now we can apply the secret to our cluster.

Installing the ArgoCD Image Updater

With everything prepared, we can now install the ArgoCD Image Updater:

kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/master/manifests/install.yaml

This creates various resources, including RBAC roles and ConfigMaps - which we’ll adjust next.

Configuring Registry Access

To work with your registry, the Image Updater needs access to the secret we just created. There are two options to configure this:

  1. Direct annotation in each Application:
argocd-image-updater.argoproj.io/backend.pull-secret: pullsecret:argocd/registry-credentials
  1. Global reference in the Image Updater’s ConfigMap

Direct annotations are useful if different applications use different registries - this keeps access fine-grained. For shared credentials, the global option is simpler and more efficient in small or homogeneous setups.

We’ll go with the second approach here. Create a configmap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-image-updater-config
  namespace: argocd
data:
  registries.conf: |
    registries:
      - name: GitLab Registry
        api_url: <Registry URL>
        prefix: <Registry URL without https>
        credentials: pullsecret:argocd/registry-credentials
        default: true
        insecure: false    

Deploy it with:

kubectl apply -f configmap.yaml

At this point you should no longer see any registry-related errors in the logs.

Seeing it in action

Now let’s test the setup: Trigger your pipeline so it builds and pushes a new image to the registry. Important: if you’ve been using generic tags like latest, you should switch to a unique tag like the commit short SHA ($CI_COMMIT_SHORT_SHA). This avoids caching issues and makes deployments traceable.

Once the pipeline is done, check your cluster again. After a short wait, you should see a deployment with the new image – no manual deploy step needed. 🎉

Optimization Options

To keep things simple, we used a fairly basic setup. But the Image Updater offers many customization options:

  • filtering or excluding tags
  • writing changes back to Git (write-back-method: git)
  • updating Kustomize files instead of Helm values

This makes it easy to adapt the controller’s behavior to the structure of your repository. More details on configuration can be found in the official docs .

Update (after this article was written)

With the v1.0.0 release, the Image Updater has moved from annotation-based configuration to a CRD-based config. I might play around with this later - and keep you posted then :)