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.
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
argocdCLI 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
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
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+repoURLpointing to an OCI or HTTP chart repo - Kustomize: Path points to a directory with
kustomization.yaml— auto-detected - Plain YAML directory: Applies all
.yamlfiles in the path - Jsonnet: Path points to
.jsonnetfiles - 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
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.
Exercises
helm.parameters. Verify the deployment matches your overrides.
Key Takeaways & Next Steps
- 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: truemakes Git the single source of truth — manual changes are reverted automaticallyautomated.prune: truedeletes resources that were removed from Git- Use
ignoreDifferencesfor 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.