Back to Distributed Systems & Kubernetes Series

Argo CD Track Part 1: Concepts & Install

June 6, 2026 Wasil Zafar 24 min read

Push-based CI/CD — where your pipeline runs kubectl apply — is fragile, hard to audit, and gives no visibility into what's actually running. GitOps solves this by making Git the source of truth and Argo CD the reconciliation engine. In this part we install Argo CD and deploy grade-api the GitOps way.

Table of Contents

  1. What is GitOps?
  2. Argo CD Architecture
  3. Installing Argo CD
  4. The Application CRD
  5. First Application: grade-api
  6. Drift Detection
  7. Exercises
  8. Key Takeaways & Next Steps

What is GitOps?

Push vs Pull: The Fundamental Shift

Traditional CI/CD is push-based: your pipeline builds, tests, then pushes the deployment into the cluster. The pipeline has credentials to the cluster. The cluster has no record of what the pipeline deployed. If someone runs kubectl edit deployment grade-api directly, the change is invisible and the pipeline will overwrite it on the next run.

GitOps is pull-based: an agent running inside the cluster continuously pulls the desired state from Git and reconciles the cluster to match. No external system needs cluster access. The cluster's actual state is always compared against Git's declared state.

Push-Based CD vs GitOps Pull-Based CD
flowchart LR
    subgraph PUSH ["Push-Based (Traditional)"]
        direction LR
        C1[Code Change] --> CI1[CI Pipeline]
        CI1 -->|kubectl apply| K1[Kubernetes]
        CI1 -->|needs cluster creds| K1
    end

    subgraph PULL ["GitOps Pull-Based"]
        direction LR
        C2[Code Change] --> CI2[CI Pipeline]
        CI2 -->|git push| GIT[Git Repo]
        GIT -.->|Argo CD polls| AGENT[Argo CD Agent]
        AGENT -->|reconciles| K2[Kubernetes]
        AGENT -->|no external creds needed| K2
    end

    style PUSH fill:#fff5f5,stroke:#BF092F
    style PULL fill:#f0f8f0,stroke:#3B9797
                            

The Four GitOps Principles (OpenGitOps)

  • Declarative: The entire system — apps, infrastructure, config — is described declaratively. You describe what you want, not how to achieve it.
  • Versioned and Immutable: Desired state is stored in Git, providing an immutable history. Every change is a commit. Rollback is git revert.
  • Pulled Automatically: Software agents automatically pull the desired state from Git and apply it. No one pushes into the cluster.
  • Continuously Reconciled: Agents continuously ensure the actual state matches the desired state. Drift is detected and corrected automatically.

Argo CD Architecture

Core Components

# Argo CD runs as several Deployments in the argocd namespace
kubectl get pods -n argocd

# NAME                                            READY   STATUS
# argocd-application-controller-0                 1/1     Running
# argocd-applicationset-controller-xxx            1/1     Running
# argocd-dex-server-xxx                           1/1     Running
# argocd-notifications-controller-xxx             1/1     Running
# argocd-redis-xxx                                1/1     Running
# argocd-repo-server-xxx                          1/1     Running
# argocd-server-xxx                               1/1     Running
  • argocd-application-controller: The heart of Argo CD. Watches Application CRDs, compares live state against Git, triggers syncs. Runs as a StatefulSet — one per cluster.
  • argocd-server: Serves the web UI and the gRPC/HTTP API. The entry point for the argocd CLI and the UI.
  • argocd-repo-server: Clones Git repos, renders templates (Helm, Kustomize, plain YAML, Jsonnet), and caches the results. Isolated for security — never touches the cluster API directly.
  • argocd-applicationset-controller: Generates Application objects from generators (Git directories, GitHub PRs, clusters, etc.). Covered in Part 3.
  • argocd-notifications-controller: Sends Slack/email/Teams notifications on sync events. Covered in Part 5.
  • argocd-dex-server: OIDC provider proxy for SSO integration (GitHub, Google, Okta, etc.).

The Sync Loop

Argo CD Reconciliation Loop
sequenceDiagram
    participant GIT as Git Repository
    participant REPO as argocd-repo-server
    participant CTRL as application-controller
    participant K8S as Kubernetes API

    loop Every 3 minutes (default)
        CTRL->>GIT: Poll for changes (or webhook trigger)
        GIT-->>REPO: Git clone / fetch
        REPO->>REPO: Render templates (Helm/Kustomize)
        REPO-->>CTRL: Desired state manifests
        CTRL->>K8S: Get current live state
        K8S-->>CTRL: Live resource state
        CTRL->>CTRL: Diff desired vs live
        alt Drift detected (OutOfSync)
            CTRL->>K8S: Apply desired state (if autoSync)
            K8S-->>CTRL: Apply result
        else In sync
            CTRL->>CTRL: Status: Synced ✓
        end
    end
                            

Installing Argo CD

Install with Helm

# Create namespace
kubectl create namespace argocd

# Add Argo Helm repo
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update

# Install Argo CD
helm install argocd argo/argo-cd \
  --namespace argocd \
  --version 7.x.x \
  --set server.service.type=LoadBalancer \
  --wait

# For local development (no LoadBalancer needed):
helm install argocd argo/argo-cd \
  --namespace argocd \
  --set server.service.type=NodePort \
  --wait

# Alternative: plain manifests (fixed version, good for air-gap)
kubectl apply -n argocd \
  -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
Managing Argo CD with Argo CD: Once you understand the basics, Argo CD should manage itself. Create an Application that points to your Argo CD Helm chart values in Git — when you change the values file and push, Argo CD self-upgrades. This is the recommended production pattern called "managing Argo CD declaratively."

argocd CLI

# Install argocd CLI
# macOS
brew install argocd

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

# Windows (Scoop)
scoop install argocd

# Get initial admin password (stored in a Secret)
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d; echo

# Login
argocd login localhost:8080 \
  --username admin \
  --password  \
  --insecure    # only for local dev

# Change password
argocd account update-password

Accessing the UI

# Port-forward the API server to localhost
kubectl port-forward svc/argocd-server -n argocd 8080:443

# Open https://localhost:8080
# Username: admin
# Password: from argocd-initial-admin-secret

# OR: get the LoadBalancer IP
kubectl get svc argocd-server -n argocd
# NAME            TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)
# argocd-server   LoadBalancer   10.96.0.1      34.1.2.3       443:30080/TCP

# Access at https://34.1.2.3

The Application CRD

Application Anatomy

Everything Argo CD manages is represented as an Application CRD. This is a Kubernetes resource that declares: where to get the desired state (source) and where to deploy it (destination):

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: grade-api-prod
  namespace: argocd      # Applications always live in the argocd namespace
  finalizers:
  - resources-finalizer.argocd.argoproj.io  # Cascade delete on app deletion
spec:
  # ── SOURCE: where the desired state comes from ──────────────────
  source:
    repoURL: https://github.com/your-org/grade-api-gitops
    targetRevision: HEAD      # Branch, tag, or commit SHA
    path: helm/grade-api      # Path within the repo to the Helm chart or YAML

    # Helm-specific source configuration
    helm:
      releaseName: grade-api
      valueFiles:
        - values.yaml
        - values-prod.yaml
      parameters:
        - name: image.tag
          value: "1.2.0"

  # ── DESTINATION: where to deploy ────────────────────────────────
  destination:
    server: https://kubernetes.default.svc  # in-cluster server
    namespace: production

  # ── SYNC POLICY: how Argo CD behaves ────────────────────────────
  syncPolicy:
    automated:
      prune: true      # Delete resources removed from Git
      selfHeal: true   # Revert manual changes to cluster
    syncOptions:
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground
      - ServerSideApply=true   # Use SSA for large resources
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

  # ── HEALTH CHECKS: override built-in health assessment ──────────
  ignoreDifferences:
  - group: apps
    kind: Deployment
    jsonPointers:
    - /spec/replicas   # Ignore replica count (managed by HPA)

Source Types Argo CD Supports

  • Helm chart in Git: Path points to a directory with Chart.yaml
  • Helm chart from registry: Use chart + repoURL pointing to an OCI or HTTP chart repo
  • Kustomize: Path points to a directory with kustomization.yaml — auto-detected
  • Plain YAML directory: Applies all .yaml files in the path
  • Jsonnet: Path points to .jsonnet files
  • Multiple sources (v2.6+): Combine a Helm chart from registry with values from a separate Git repo

First Application: grade-api

Git Repository Setup

GitOps requires a Git repository that holds your Kubernetes manifests or Helm values. The recommended pattern is a separate repo from your application code — this is the "GitOps repo" or "config repo":

# Recommended GitOps repo structure
grade-api-gitops/
├── helm/
│   └── grade-api/            ← Helm chart (or symlink to published chart)
│       ├── Chart.yaml
│       ├── values.yaml        ← base values
│       └── templates/
├── environments/
│   ├── dev/
│   │   └── values.yaml        ← dev-specific overrides
│   ├── staging/
│   │   └── values.yaml
│   └── production/
│       └── values.yaml
└── apps/                      ← Argo CD Application manifests
    ├── grade-api-dev.yaml
    ├── grade-api-staging.yaml
    └── grade-api-prod.yaml
# environments/production/values.yaml
replicaCount: 4
image:
  tag: "1.2.0"
ingress:
  enabled: true
  host: grades.company.com
autoscaling:
  enabled: true
  minReplicas: 4
  maxReplicas: 20

Creating the Application

Three ways to create an Application — all equivalent:

# Method 1: argocd CLI
argocd app create grade-api-prod \
  --repo https://github.com/your-org/grade-api-gitops \
  --path helm/grade-api \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace production \
  --helm-set-string image.tag=1.2.0 \
  --values environments/production/values.yaml \
  --sync-policy automated \
  --auto-prune \
  --self-heal

# Method 2: kubectl apply (declarative — RECOMMENDED)
kubectl apply -f - <<'EOF'
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: grade-api-prod
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  source:
    repoURL: https://github.com/your-org/grade-api-gitops
    targetRevision: HEAD
    path: helm/grade-api
    helm:
      valueFiles:
        - ../../environments/production/values.yaml
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
EOF

# Method 3: Argo CD UI
# Navigate to + NEW APP in the web UI

Manual & Auto Sync

# Check application status
argocd app get grade-api-prod

# Name:               grade-api-prod
# Project:            default
# Server:             https://kubernetes.default.svc
# Namespace:          production
# URL:                https://argocd.company.com/applications/grade-api-prod
# Repo:               https://github.com/your-org/grade-api-gitops
# Target:             HEAD
# Path:               helm/grade-api
# SyncWindow:         Sync Allowed
# Sync Policy:        Automated (Prune)
# Sync Status:        Synced to  (abcd1234)
# Health Status:      Healthy

# Manual sync (when autoSync is disabled)
argocd app sync grade-api-prod

# Sync with replace (force recreate — dangerous!)
argocd app sync grade-api-prod --force

# Wait for sync to complete
argocd app wait grade-api-prod --health

# List all apps
argocd app list

# Watch live status
argocd app get grade-api-prod --watch

Drift Detection

One of Argo CD's most powerful features: it continuously compares live cluster state against Git. If someone manually edits a resource, Argo CD detects the drift and (if selfHeal is enabled) reverts it:

# Manually scale down grade-api in production
kubectl scale deployment grade-api \
  --replicas=1 \
  --namespace production

# Argo CD immediately detects drift
argocd app get grade-api-prod
# Sync Status: OutOfSync
# Message: deployment/grade-api: spec.replicas: 1 != 4

# If selfHeal=true: Argo CD auto-reverts to replicas=4 within seconds
# If selfHeal=false: you see the OutOfSync state until manual sync

# See exactly what drifted
argocd app diff grade-api-prod
# === apps/Deployment production/grade-api ===
# -  replicas: 4
# +  replicas: 1

# Ignore drift for specific fields (e.g., HPA manages replicas)
# Add to Application spec:
# ignoreDifferences:
# - group: apps
#   kind: Deployment
#   jsonPointers:
#   - /spec/replicas
Real World

selfHeal: When to Enable, When to Disable

Enable selfHeal: true for production applications — it enforces Git as the source of truth. The only exception: if your HPA is managing replicas, add /spec/replicas to ignoreDifferences to prevent Argo CD from fighting the HPA. Disable selfHeal during incident response when you need to make emergency changes directly to the cluster — but remember to revert by pushing to Git afterward.

HPA Incident Response GitOps Compliance

Exercises

Exercise 1 — Install & First App: Install Argo CD in your local cluster. Create a public GitHub repo with a simple nginx Deployment and Service YAML. Create an Argo CD Application pointing to that repo. Verify Argo CD deploys nginx and the Application status shows "Synced / Healthy."
Exercise 2 — Drift Detection: With the nginx Application running, manually scale the Deployment to 0 replicas with kubectl. Observe the Application status change to "OutOfSync." Enable selfHeal and watch Argo CD restore the replicas. Disable selfHeal and try again — confirm it stays OutOfSync.
Exercise 3 — Helm Source: Create a GitOps repo with the grade-api Helm chart from the Helm track. Create an Argo CD Application that uses it as a Helm source. Override the replica count and image tag via helm.parameters. Verify the deployment matches your overrides.

Key Takeaways & Next Steps

Key Takeaways:
  • GitOps is pull-based: the agent inside the cluster pulls from Git, not the pipeline pushing in
  • Argo CD's Application CRD declares source (Git/Helm), destination (cluster/namespace), and sync policy
  • automated.selfHeal: true makes Git the single source of truth — manual changes are reverted automatically
  • automated.prune: true deletes resources that were removed from Git
  • Use ignoreDifferences for HPA-managed replicas and other externally-managed fields
  • Always manage Argo CD Applications declaratively (kubectl apply) — not imperatively via CLI

Next in This Track

In Part 2: Applications & Sync, we go deeper into Application management — multiple value files, the multi-source Application pattern, sync windows, resource hooks, and the UI walkthrough for monitoring application health.