Argo CD can update your Kubernetes deployments with new container images, but getting the image tags into Argo CD automatically from your GitLab CI pipeline is a common hurdle.

Let’s see this in action. Imagine a simple pipeline that builds a Docker image and then pushes it to a registry. The CI_COMMIT_SHORT_SHA is a unique identifier for this commit.

stages:
  - build
  - deploy

variables:
  IMAGE_NAME: registry.example.com/my-app

build-image:
  stage: build
  script:
    - docker build -t $IMAGE_NAME:$CI_COMMIT_SHORT_SHA .
    - docker push $IMAGE_NAME:$CI_COMMIT_SHORT_SHA
    - echo "IMAGE_TAG=$CI_COMMIT_SHORT_SHA" >> build.env # Export the tag for later stages

deploy-to-argo:
  stage: deploy
  needs:
    - build-image
  variables:
    # This variable will be populated from the build.env file
    IMAGE_TAG: ""
  script:
    # Here's where we'll interact with Argo CD
    - echo "Updating Argo CD with image tag: $IMAGE_TAG"
    # ... Argo CD update commands will go here ...

The core problem Argo CD solves here is drift detection. It monitors your Git repository for the desired state of your applications and compares it to the live state in Kubernetes. When you manually update an image tag in your Kubernetes manifests (e.g., deployment.yaml), you’re telling Argo CD "this is the new desired state." Automating this tag update means your CI pipeline becomes the source of truth for the desired image version.

The "magic" happens when Argo CD synchronizes. It compares the spec.template.spec.containers[].image field in your Deployment (or StatefulSet, etc.) against the image specified in your Git repository. If they differ, Argo CD flags it as "OutOfSync" and can be configured to automatically sync, or you can trigger a manual sync.

To automate this, your CI pipeline needs to do two things:

  1. Build and push the new image. This is standard CI practice.
  2. Update the image tag in your Kubernetes manifests within your Git repository. This is the part Argo CD watches.

Here’s a common workflow:

1. Application Repository Structure:

Your application repository should contain not just your application code, but also your Kubernetes manifests.

my-app/
├── src/
├── Dockerfile
├── k8s/
│   └── deployment.yaml
├── .gitlab-ci.yml
└── ...

Your deployment.yaml would look something like this:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app-container
        image: registry.example.com/my-app:latest # This tag will be updated
        ports:
        - containerPort: 8080

2. Updating the Manifests in CI:

The most straightforward way to update the image tag in your deployment.yaml is by using kustomize or sed within your GitLab CI. kustomize is generally preferred for managing Kubernetes configurations.

Let’s assume you have a kustomization.yaml in your k8s/ directory:

# k8s/kustomization.yaml
resources:
  - deployment.yaml

images:
  - name: registry.example.com/my-app
    newTag: 1.0.0 # This will be updated by CI

And your GitLab CI job would look like this:

deploy-to-argo:
  stage: deploy
  needs:
    - build-image
  variables:
    IMAGE_TAG: "" # Will be populated from build.env
    # Assuming your kustomization.yaml is in k8s/ directory
    KUSTOMIZATION_PATH: "k8s"
  script:
    - echo "Building image and exporting tag..."
    - # ... docker build and push commands ...
    - echo "IMAGE_TAG=$CI_COMMIT_SHORT_SHA" >> build.env

  # This is the crucial part: updating the image tag in kustomization.yaml
  - echo "Updating image tag in kustomization.yaml..."
  - export IMAGE_TAG=$(cat build.env | grep IMAGE_TAG= | cut -d= -f2)
  - echo "Using tag: $IMAGE_TAG"
  - |
    cd $KUSTOMIZATION_PATH
    kustomize edit set image registry.example.com/my-app=$IMAGE_NAME:$IMAGE_TAG
    cd ..

  # Commit the changes back to your Git repository
  - git config --global user.email "gitlab-ci@example.com"
  - git config --global user.name "GitLab CI"
  - git add $KUSTOMIZATION_PATH/kustomization.yaml
  - git commit -m "Update image tag to $IMAGE_TAG [skip ci]"
  - git push origin HEAD:$CI_COMMIT_BRANCH

  # Trigger Argo CD sync (optional, depends on your Argo CD setup)
  # Example using Argo CD CLI:
  # - argocd app sync my-app-application --grpc-web

The kustomize edit set image command is powerful. It directly modifies the kustomization.yaml file, replacing the newTag for the specified image. After committing this change, Argo CD will detect the modification in your Git repository and initiate a sync. The [skip ci] in the commit message prevents an infinite loop where the commit itself triggers another pipeline run.

Alternative using sed (less robust):

If you’re not using kustomize, you can use sed to directly modify your deployment.yaml.

# ... in your deploy-to-argo job ...
  - export IMAGE_TAG=$(cat build.env | grep IMAGE_TAG= | cut -d= -f2)
  - sed -i "s|registry.example.com/my-app:.*|registry.example.com/my-app:$IMAGE_TAG|g" k8s/deployment.yaml
  - git add k8s/deployment.yaml
  - git commit -m "Update image tag to $IMAGE_TAG [skip ci]"
  - git push origin HEAD:$CI_COMMIT_BRANCH

Argo CD Application Configuration:

Your Argo CD Application resource will be configured to point to this Git repository and the specific path containing your kustomization.yaml or deployment.yaml.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app-application
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://gitlab.com/your-group/my-app.git
    targetRevision: HEAD
    path: k8s # Path to your kustomization.yaml or deployment.yaml
    # If using kustomize:
    # kustomize:
    #   imageTransformers:
    #   - name: registry.example.com/my-app
    #     newValue: registry.example.com/my-app:$CI_COMMIT_SHORT_SHA # This is an alternative to modifying the file directly
  destination:
    server: https://kubernetes.default.svc
    namespace: my-app-namespace
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
    - CreateNamespace=true

Notice the kustomize.imageTransformers in the Argo CD Application spec. This is another way to handle image updates, where Argo CD itself transforms the image tag directly without you needing to commit a change to Git. However, committing the change to Git provides a more explicit audit trail and allows Argo CD’s drift detection to work more traditionally.

The most common pitfall is forgetting to add the [skip ci] to your commit message, leading to pipelines that never finish. Another is ensuring your Git user is properly configured in CI and that the runner has permissions to push to the repository.

Once this is set up, your next challenge will be managing multiple environments (dev, staging, prod) with different image tags and configurations.

Want structured learning?

Take the full Gitlab-ci course →