Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

16.4 ArgoCD — Declarative CD for Kubernetes

⏱️ ~8 min read

TL;DR: ArgoCD is a GitOps continuous delivery controller that runs inside your cluster. It watches a Git repository, compares the desired state (Git) with the actual state (cluster), and automatically syncs the difference. Every deployment, rollback, and status check goes through ArgoCD’s UI or CLI — kubectl apply in production becomes a thing of the past.


ArgoCD Architecture

graph TD
    subgraph "Git Repository"
        REPO["Config Repo\n(Kustomize / Helm / raw YAML)"]
    end

    subgraph "Kubernetes Cluster"
        API["API Server"]
        subgraph "argocd namespace"
            APPCTRL["Application\nController\n(reconcile loop)"]
            APISERVER["ArgoCD\nAPI Server"]
            REPOSERVER["Repo\nServer\n(render templates)"]
            APPSET["ApplicationSet\nController"]
        end
        YOURNS["Your\nNamespace\n(Deployments, Services…)"]
    end

    subgraph "Users"
        UI["ArgoCD UI\n(browser)"]
        ARGOCLI["argocd CLI"]
    end

    REPO -->|"poll / webhook"| REPOSERVER
    REPOSERVER -->|"rendered YAML"| APPCTRL
    APPCTRL -->|"compare + apply"| API
    API --> YOURNS
    UI & ARGOCLI -->|"manage apps"| APISERVER
    APISERVER --> APPCTRL

Core Concepts

ConceptDescription
ApplicationAn ArgoCD CRD that maps a Git repo path to a cluster namespace
AppProjectGroups Applications; defines RBAC and source/destination restrictions
SyncThe act of applying the Git state to the cluster
Sync StatusSynced / OutOfSync — does Git match the cluster?
Health StatusHealthy / Degraded / Progressing — are cluster resources working?
Auto-SyncAutomatically apply Git changes without manual approval
Self-HealRevert manual cluster changes back to Git state

Installing ArgoCD

# Create namespace and install
kubectl create namespace argocd
kubectl apply -n argocd -f \
  https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Wait for all pods to be Running
kubectl rollout status deployment/argocd-server -n argocd
kubectl get pods -n argocd

# Get the initial admin password
kubectl get secret argocd-initial-admin-secret -n argocd \
  -o jsonpath="{.data.password}" | base64 -d && echo

# Port-forward the ArgoCD UI
kubectl port-forward svc/argocd-server -n argocd 8080:443

# Or install the argocd CLI
curl -sSL -o argocd \
  https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
chmod +x argocd && sudo mv argocd /usr/local/bin/

# Login via CLI
argocd login localhost:8080 \
  --username admin \
  --password $(kubectl get secret argocd-initial-admin-secret -n argocd \
    -o jsonpath="{.data.password}" | base64 -d) \
  --insecure

Defining an Application

An Application CRD is the core ArgoCD object. It says: “track this Git path and apply it to this cluster/namespace.”

Kustomize Application

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app-production
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io   # Clean up resources on deletion
spec:
  project: default

  source:
    repoURL: https://github.com/myorg/k8s-config.git
    targetRevision: HEAD                       # Branch, tag, or commit SHA
    path: overlays/production                  # Path within the repo

  destination:
    server: https://kubernetes.default.svc    # In-cluster (same cluster ArgoCD is in)
    namespace: production

  syncPolicy:
    automated:                                 # Auto-sync on Git changes
      prune: true                              # Delete resources removed from Git
      selfHeal: true                           # Revert manual cluster changes
    syncOptions:
    - CreateNamespace=true                     # Create namespace if missing
    - PrunePropagationPolicy=foreground        # Wait for resources to be deleted
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

Helm Application

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: monitoring
  namespace: argocd
spec:
  source:
    repoURL: https://prometheus-community.github.io/helm-charts
    chart: kube-prometheus-stack
    targetRevision: "55.5.0"                   # Pinned chart version
    helm:
      releaseName: monitoring
      values: |
        grafana:
          adminPassword: supersecret
        prometheus:
          prometheusSpec:
            retention: 7d
  destination:
    server: https://kubernetes.default.svc
    namespace: monitoring
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
    - CreateNamespace=true

ArgoCD CLI — Day-to-Day Operations

# List all applications
argocd app list

# Check app status
argocd app get my-app-production

# Manual sync (when auto-sync is off)
argocd app sync my-app-production

# Sync only a specific resource
argocd app sync my-app-production --resource Deployment:my-app

# Check what would change (diff, like helm diff)
argocd app diff my-app-production

# Roll back to a previous revision
argocd app history my-app-production        # List revisions
argocd app rollback my-app-production 3     # Roll back to revision 3

# Hard refresh (clear cache, re-fetch from Git)
argocd app get my-app-production --hard-refresh

# Delete an application (and its resources if finalizer is set)
argocd app delete my-app-production

Sync Waves and Hooks — Controlling Deployment Order

For ordered deployments (e.g., run DB migrations before deploying app):

# 1. Database migration Job runs first (wave -1)
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
  annotations:
    argocd.argoproj.io/sync-wave: "-1"    # Lower number = runs first
    argocd.argoproj.io/hook: PreSync      # Only run before sync
    argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
  template:
    spec:
      containers:
      - name: migrate
        image: my-app:sha-a3f8c1d
        command: ["python", "manage.py", "migrate"]
      restartPolicy: Never
---
# 2. Application Deployment (wave 0, default)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  annotations:
    argocd.argoproj.io/sync-wave: "0"
HookWhen It Runs
PreSyncBefore any resources are applied
SyncDuring the sync, alongside other resources
PostSyncAfter all resources are healthy
SyncFailIf sync fails (for cleanup/notification)

ApplicationSet — Multi-Cluster and Multi-Environment

ApplicationSet auto-generates Application objects from a template:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: my-app-all-envs
  namespace: argocd
spec:
  generators:
  - list:
      elements:
      - env: dev
        cluster: https://dev-cluster:6443
        namespace: my-app-dev
        revision: main
      - env: staging
        cluster: https://staging-cluster:6443
        namespace: my-app-staging
        revision: main
      - env: production
        cluster: https://prod-cluster:6443
        namespace: my-app-prod
        revision: v1.2.3          # Prod is pinned to a release tag
  template:
    metadata:
      name: "my-app-{{env}}"
    spec:
      project: default
      source:
        repoURL: https://github.com/myorg/k8s-config.git
        targetRevision: "{{revision}}"
        path: "overlays/{{env}}"
      destination:
        server: "{{cluster}}"
        namespace: "{{namespace}}"
      syncPolicy:
        automated:
          prune: true
          selfHeal: "{{env}}" != "production"  # Manual sync for prod

✅ Quick Check

Q1: What’s the difference between Synced and Healthy in ArgoCD?

Answer **Synced** means the cluster resources **match** what's declared in Git (the desired state). **Healthy** means those resources are **working correctly** — pods are Running, Deployments have the expected number of ready replicas, Services have endpoints, etc. An app can be `Synced` but `Degraded` (e.g., the manifest is correctly applied but pods are crash-looping). It can also be `OutOfSync` but `Healthy` (someone manually edited a ConfigMap — Git doesn't match, but everything still works).

Q2: You have selfHeal: true enabled. An on-call engineer adds a temporary environment variable to a Deployment using kubectl edit during an incident. What happens?

Answer ArgoCD detects the drift within the next reconciliation cycle (default: every 3 minutes) and **reverts the manual change** — removing the env var. This is by design: in GitOps, Git is the single source of truth. For emergencies, the correct approach is to make the change in Git (fast PR or direct commit to a branch), let ArgoCD sync it, and revert when the incident is over. Some teams disable `selfHeal` for production to allow temporary manual overrides, at the cost of drift risk.

Q3: How does ArgoCD enable rolling back a deployment without running kubectl rollout undo?

Answer ArgoCD tracks the Git revision history of the config repo. Each sync corresponds to a specific Git commit. To roll back, you use `argocd app rollback my-app REVISION` which instructs ArgoCD to sync to a previous commit's state, applying all the manifests from that commit — not just the Deployment template. This is more complete than `kubectl rollout undo` which only reverts the pod template spec, not ConfigMaps, Services, or other resources that may have changed.