Back to blog

February 20, 2024 • 6 min read

GitOps for Kubernetes with ArgoCD

How I run Kubernetes delivery with GitOps, ArgoCD, and image automation.

Prerequisites#

Before you dive in, you should have:

  • A running Kubernetes cluster and basic tooling
  • Working knowledge of Git and CI/CD
  • Optional but helpful: GitFlow

GitOps#

GitOps means Git is the source of truth for both app and infra state. You declare the desired state in a repository, then a controller like ArgoCD keeps the cluster aligned with that state.

Diagram explaining the GitOps workflow
Figure 1: GitOps workflow - declarative configuration management with Git as source of truth.

Key concepts#

  • Declarative config: manifests define what should exist.
  • Versioned changes: every infrastructure or app config change is tracked in Git.
  • Automatic sync: controllers detect drift and apply desired state.
  • Reconciliation loop: desired state and live state keep converging.
Diagram of the GitOps reconciliation loop
Figure 2: Reconciliation loop - continuous synchronization between desired and actual state.

Push vs pull#

Most teams use one of two strategies: push or pull.

Push strategy#

  • A pipeline pushes changes to the target environment.
  • You control exactly when apply happens.
  • Common example: Terraform apply in CI.
Diagram of the Terraform push-based GitOps flow
Figure 3: Terraform push-based flow - automated infrastructure changes triggered by Git commits.

Pull strategy#

  • The target environment continuously pulls desired state from Git.
  • Drift is corrected automatically through reconciliation.
  • Common example: ArgoCD watching Kubernetes manifests.

Comparison#

Push StrategyPull Strategy
Fine-grained control over when changes are appliedChanges are applied periodically, unless webhooks are configured
Simple to implement and understandSlightly more complex architecture
Immediate feedback on the success or failure of the changesFeedback is given when changes are periodically applied, with automatic rollback when failed
Desired and actual state can driftDesired and actual state stay aligned through reconciliation

ArgoCD#

  • ArgoCD is a GitOps CD tool for Kubernetes.
  • It gives you both a UI and CLI to inspect sync state, deploy, and roll back.
  • It works well with Helm and Kustomize.
  • State is persisted in etcd. Redis is cache only, so it can be rebuilt. More details here.
  • In pull mode, ArgoCD polls Git, compares desired state with cluster state, and syncs when needed.

Here is how it works:

Diagram of the ArgoCD pull-based GitOps flow
Figure 4: ArgoCD pull-based flow - continuous monitoring and synchronization from Git to Kubernetes cluster.

ArgoCD uses Application and ApplicationSet resources to keep deployments declarative. Here is a basic example. Official docs are here.

Install ArgoCD helm chart#

Using Helm CLI#

helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd

Using Terraform#

resource "helm_release" "argocd" {
  name = "argocd"

  repository       = "https://argoproj.github.io/argo-helm"
  chart            = "argo-cd"
  version          = ""
  namespace        = "argocd"
  create_namespace = true
}

Create an ArgoCD application#

You can also apply this with kubectl or the Terraform kubernetes_manifest resource.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: guestbook
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/argoproj/argocd-example-apps.git
    targetRevision: HEAD
    path: guestbook
  destination:
    server: https://kubernetes.default.svc
    namespace: guestbook
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
      allowEmpty: false
    syncOptions:
      - Validate=true
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground
      - PruneLast=true

This setup deploys the ArgoCD example app from a specific repository path and target revision. If the repo is private, configure credentials with a secret. A sample is available here.

You can reference that secret with:

"argocd-image-updater.argoproj.io/write-back-method" = "git:secret:argocd/${local.repo_secret_name}"

The sync options add guardrails and make behavior explicit:

  • Automated sync:

    • prune: removes resources no longer defined in Git.
    • selfHeal: reverts manual cluster changes to match desired state.
    • allowEmpty: blocks sync if the source is empty.
  • Sync options:

    • Validate=true: validates manifests before applying.
    • PrunePropagationPolicy=foreground: runs pruning in the foreground.
    • CreateNamespace=true: creates the namespace if it does not exist.
    • PruneLast=true: prunes resources last during sync.

GitOps workflow with ArgoCD#

  • Configuration in Git: teams commit desired app and infra state.
  • Synchronization: ArgoCD reconciles that state into the cluster.
  • Rollback and history: deployment history stays available for audit and recovery.

Argo Image Updater#

Argo Image Updater updates image tags in GitOps manifests automatically. This helps keep environments fresh without hand-editing tags.

Diagram of the ArgoCD Image Updater workflow
Figure 5: ArgoCD Image Updater flow - automated container image updates in Kubernetes manifests.

Install Argo Image Updater helm chart#

Using Helm CLI#

helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd-image-updater argo/argocd-image-updater -f values.yaml

Using Terraform#

resource "helm_release" "argo_image_updater" {
  name = "argo-image-updater"

  repository       = "https://argoproj.github.io/argo-helm"
  chart            = "argocd-image-updater"
  namespace        = "argocd"
  create_namespace = true
  version          = ""

  values = [templatefile(
    "${path.module}/resources/values.yaml",
    {
      AWS_REGION           = var.aws_region
      AWS_ACCOUNT_ID       = var.aws_account_id
      IAM_ROLE_NAME        = aws_iam_role.argo_image_updater_role.name
      BITBUCKET_PUSH_EMAIL = var.bitbucket_push_email
    }
  )]
}

Image updater values file#

This example uses AWS ECR as the image registry. Replace the registry configuration for your setup.

metrics:
  enabled: true

config:
  registries:
    - name: ECR
      api_url: https://${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com
      prefix: ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com
      default: true
      ping: yes
      insecure: no
      credentials: ext:/scripts/ecr-login.sh
      credsexpire: 11h

  # Email used to commit image updates to the GitOps repository
  gitCommitMail: ${BITBUCKET_PUSH_EMAIL}

authScripts:
  enabled: true
  scripts:
    ecr-login.sh: |
      #!/bin/sh
      aws ecr --region $AWS_REGION get-authorization-token --output text \
        --query 'authorizationData[].authorizationToken' | base64 -d

extraEnv:
  - name: AWS_REGION
    value: ${AWS_REGION}

serviceAccount:
  create: true
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::${AWS_ACCOUNT_ID}:role/${IAM_ROLE_NAME}

These annotations tell Argo Image Updater which image and Helm value to update. Add them to the ArgoCD Application resource.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: guestbook
  namespace: argocd
  annotations:
    "argocd-image-updater.argoproj.io/image-list": "=${var.aws_account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/"
    "argocd-image-updater.argoproj.io/admin-api.helm.image-tag": ".image.tag"
spec: ...

Image update policies#

  • In an Application manifest, annotations define image update policy with regex rules like <image>.allow-tags. Full list here.
  • This lets you control which tags are valid per environment. For example, development can accept only snapshot tags:
argocd-image-updater.argoproj.io/.allow-tags: "regexp:2\.\d+\.\d+-SNAPSHOT"

I recommend making these rules explicit early. It avoids accidental promotion of the wrong tag class.


Summary#

GitOps with ArgoCD gives you a cleaner operating model for Kubernetes. Desired state stays in Git, reconciliation handles drift, and rollbacks are straightforward.

When you add Argo Image Updater, image promotion also becomes policy-driven instead of manual. Less operational noise, more predictable deploys.