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:
- Build and push the new image. This is standard CI practice.
- 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.