Introduction — Why Argo CD Is the De Facto GitOps Controller
In Part 5, we established the GitOps philosophy — Git as the single source of truth for infrastructure and application state. Now we turn theory into practice with Argo CD, the CNCF-graduated project that has become the industry standard for Kubernetes-native continuous delivery.
Argo CD implements the GitOps pattern by continuously monitoring Git repositories and reconciling the desired state declared in manifests against the live state running in your clusters. When drift is detected — whether from manual changes, failed deployments, or configuration errors — Argo CD can automatically remediate, ensuring your production environment always matches what's committed in Git.
By the end of this article, you'll be able to install Argo CD, deploy applications declaratively, configure sync policies and drift remediation, manage multi-cluster deployments with ApplicationSets, and implement progressive delivery strategies with Argo Rollouts.
Installing Argo CD
Argo CD runs entirely within Kubernetes. The recommended installation uses the official manifests or Helm chart. Let's cover both approaches.
Method 1: Plain Manifests
# Create the argocd namespace
kubectl create namespace argocd
# Install Argo CD (stable release)
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Verify all pods are running
kubectl get pods -n argocd
# NAME READY STATUS RESTARTS AGE
# argocd-application-controller-0 1/1 Running 0 45s
# argocd-dex-server-6dcf645b6b-x2j4k 1/1 Running 0 45s
# argocd-redis-5b6967fdfc-7g8n2 1/1 Running 0 45s
# argocd-repo-server-7598bf5999-4kl8m 1/1 Running 0 45s
# argocd-server-7d56c5bf4d-9mn3p 1/1 Running 0 45s
# Retrieve initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
echo
Method 2: Helm Chart
# Add the Argo Helm repository
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
# Install with custom values
helm install argocd argo/argo-cd \
--namespace argocd \
--create-namespace \
--set server.service.type=LoadBalancer \
--set configs.params."server\.insecure"=true \
--set controller.replicas=2
# Install the Argo CD CLI
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
rm argocd-linux-amd64
# Login via CLI
argocd login localhost:8080 --username admin --password $(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d)
argocd-initial-admin-secret once you've configured SSO or updated credentials: kubectl -n argocd delete secret argocd-initial-admin-secret
Argo CD Architecture
Argo CD consists of five core components that work together to implement the GitOps reconciliation loop:
flowchart TB
subgraph External["External Systems"]
GIT[("Git Repository")]
K8S[("Kubernetes Cluster")]
IDP["Identity Provider
(OIDC/SAML)"]
end
subgraph ArgoCD["Argo CD Components"]
API["API Server
(argocd-server)"]
REPO["Repo Server
(argocd-repo-server)"]
CTRL["Application Controller
(argocd-application-controller)"]
REDIS[("Redis
Cache")]
DEX["Dex Server
(SSO)"]
end
subgraph Clients["Clients"]
UI["Web UI"]
CLI["argocd CLI"]
CICD["CI/CD Webhooks"]
end
UI --> API
CLI --> API
CICD --> API
API --> REDIS
API --> REPO
API --> DEX
DEX --> IDP
CTRL --> REPO
CTRL --> K8S
CTRL --> REDIS
REPO --> GIT
Component Responsibilities
| Component | Role | Scaling |
|---|---|---|
| API Server | gRPC/REST API, Web UI serving, RBAC enforcement, webhook handling | Horizontal (multiple replicas) |
| Repo Server | Clones Git repos, renders manifests (Helm, Kustomize, Jsonnet, plain YAML) | Horizontal (CPU-intensive) |
| Application Controller | Reconciliation loop — compares desired vs live state, triggers sync | Sharding (by cluster) |
| Redis | Caching layer for manifest generation, app state, repository credentials | Single instance (HA with Sentinel) |
| Dex | OpenID Connect provider for SSO (GitHub, LDAP, SAML, OIDC) | Horizontal |
Accessing the Dashboard
The Argo CD Web UI provides a visual representation of your application topology, sync status, and deployment history.
Port-Forwarding (Development)
# Forward the argocd-server service to localhost:8080
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Open browser at https://localhost:8080
# Login: admin / (password from initial secret)
Ingress Setup (Production)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd-server-ingress
namespace: argocd
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- argocd.example.com
secretName: argocd-server-tls
rules:
- host: argocd.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 443
Core Concepts
Argo CD introduces several domain-specific concepts that map directly to the GitOps workflow:
flowchart LR
subgraph Source["Source of Truth"]
REPO["Git Repository"]
PATH["Path / Directory"]
REV["Revision / Branch"]
end
subgraph ArgoApp["Argo CD Application"]
APP["Application CR"]
PROJ["AppProject"]
SYNC["Sync Policy"]
end
subgraph Target["Destination"]
CLUSTER["K8s Cluster"]
NS["Namespace"]
end
REPO --> APP
PATH --> APP
REV --> APP
APP --> PROJ
APP --> SYNC
APP --> CLUSTER
APP --> NS
Argo CD Terminology
- Application — A Kubernetes Custom Resource that defines the source (Git repo + path + revision) and destination (cluster + namespace) for a set of manifests
- Project — A logical grouping of Applications with RBAC boundaries, restricting which repos, clusters, and resources an Application can access
- Sync — The process of applying the desired state from Git to the target cluster
- Health — The runtime health of deployed resources (Healthy, Progressing, Degraded, Suspended, Missing)
- Refresh — Re-reading the Git repository to detect changes (hard refresh clears cache)
Creating Your First Application
Applications can be created declaratively via YAML, through the Web UI, or using the CLI. The declarative approach is preferred — it allows you to manage Argo CD itself via GitOps.
Declarative Application Manifest
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: guestbook
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
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
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
# Apply the Application manifest
kubectl apply -f guestbook-app.yaml
# Check sync status via CLI
argocd app get guestbook
# Name: argocd/guestbook
# Project: default
# Server: https://kubernetes.default.svc
# Namespace: guestbook
# URL: https://argocd.example.com/applications/guestbook
# Repo: https://github.com/argoproj/argocd-example-apps.git
# Target: HEAD
# Path: guestbook
# SyncWindow: Sync Allowed
# Sync Policy: Automated (Prune)
# Sync Status: Synced
# Health Status: Healthy
# Manually trigger sync (if auto-sync is disabled)
argocd app sync guestbook
# View application resource tree
argocd app resources guestbook
resources-finalizer.argocd.argoproj.io as a finalizer on your Application resources. This ensures that when you delete an Application, Argo CD will cascade-delete the managed Kubernetes resources in the target cluster.
Sync Policies
Sync policies determine when and how Argo CD applies changes from Git to the cluster. Getting these right is critical for both agility and safety.
Manual vs Automated Sync
| Policy | Behavior | Use Case |
|---|---|---|
| Manual | Changes detected but not applied until user triggers sync | Production environments, regulated workloads |
| Auto-Sync | Automatically syncs when Git changes are detected | Dev/staging environments, trusted repos |
| Self-Heal | Reverts manual cluster changes back to Git state | Drift prevention, compliance enforcement |
| Prune | Deletes resources removed from Git | Complete state ownership, cleanup |
Sync Waves & Hooks
Sync waves allow you to control the order in which resources are applied. Resources with lower wave numbers are synced first. Sync hooks execute at specific phases of the sync process.
# Namespace created first (wave -1)
apiVersion: v1
kind: Namespace
metadata:
name: myapp
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
# ConfigMaps and Secrets next (wave 0, default)
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: myapp
annotations:
argocd.argoproj.io/sync-wave: "0"
data:
DATABASE_URL: "postgres://db:5432/myapp"
---
# Database migration job (wave 1, PreSync hook)
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
namespace: myapp
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
template:
spec:
containers:
- name: migrate
image: myapp/migrations:latest
command: ["./migrate", "up"]
restartPolicy: Never
---
# Application deployment (wave 2)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: myapp
annotations:
argocd.argoproj.io/sync-wave: "2"
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp/api:v1.2.0
ports:
- containerPort: 8080
PreSync → Sync → PostSync → SyncFail (on error). Hook delete policies: HookSucceeded, HookFailed, BeforeHookCreation. Use PreSync for migrations, PostSync for notifications, SyncFail for rollback triggers.
Drift Detection & Remediation
Drift occurs when the live cluster state diverges from the desired state in Git. This can happen through manual kubectl edit commands, Helm operator conflicts, or external controllers modifying resources.
flowchart TD
A["Controller polls every 3min"] --> B{"Compare Git State
vs Live State"}
B -->|"Match"| C["Status: Synced ✓"]
B -->|"Drift Detected"| D["Status: OutOfSync"]
D --> E{"Self-Heal
Enabled?"}
E -->|"Yes"| F["Auto-Sync
Revert to Git State"]
E -->|"No"| G["Alert / Manual
Intervention Required"]
F --> H["Status: Synced ✓"]
G --> I["Operator Reviews
Diff in UI"]
I --> J{"Accept or
Reject?"}
J -->|"Sync"| F
J -->|"Update Git"| K["Commit Fix to Git"]
K --> A
Argo CD uses a sophisticated diff engine that understands Kubernetes resource semantics. It normalizes fields like default values, managed fields, and server-side applied annotations before comparison.
Configuring Drift Handling
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: production-api
namespace: argocd
spec:
project: production
source:
repoURL: https://github.com/myorg/k8s-manifests.git
targetRevision: main
path: apps/production/api
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
selfHeal: true # Revert drift automatically
prune: true # Remove resources deleted from Git
syncOptions:
- RespectIgnoreDifferences=true
# Ignore specific fields that external controllers manage
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas # HPA manages replicas
- group: ""
kind: Service
jqPathExpressions:
- .spec.clusterIP # Cluster-assigned field
ApplicationSets
ApplicationSets solve the scalability challenge: how do you manage hundreds of Applications across multiple clusters, environments, or teams without duplicating YAML? The ApplicationSet controller uses generators to dynamically create Application resources from templates.
Git Directory Generator
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: cluster-addons
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/myorg/cluster-addons.git
revision: HEAD
directories:
- path: addons/*
template:
metadata:
name: '{{path.basename}}'
spec:
project: infrastructure
source:
repoURL: https://github.com/myorg/cluster-addons.git
targetRevision: HEAD
path: '{{path}}'
destination:
server: https://kubernetes.default.svc
namespace: '{{path.basename}}'
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Matrix Generator (Multi-Cluster × Multi-Environment)
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: multi-env-apps
namespace: argocd
spec:
generators:
- matrix:
generators:
# Generator 1: Clusters
- clusters:
selector:
matchLabels:
env: production
# Generator 2: Apps from Git
- git:
repoURL: https://github.com/myorg/apps.git
revision: HEAD
directories:
- path: apps/*
template:
metadata:
name: '{{name}}-{{path.basename}}'
spec:
project: default
source:
repoURL: https://github.com/myorg/apps.git
targetRevision: HEAD
path: '{{path}}'
destination:
server: '{{server}}'
namespace: '{{path.basename}}'
syncPolicy:
automated:
selfHeal: true
Available Generators
- Git Generator — Creates apps from directories or files in a Git repo (most common)
- Cluster Generator — Creates apps for each registered cluster matching a selector
- Matrix Generator — Cartesian product of two generators (e.g., clusters × apps)
- Merge Generator — Combines generators with override semantics
- List Generator — Static list of parameter sets for known configurations
- Pull Request Generator — Creates preview environments for open PRs
- SCM Provider Generator — Discovers repos from GitHub/GitLab organizations
Multi-Cluster Deployment
Argo CD supports deploying to multiple Kubernetes clusters from a single management plane. External clusters are registered via the CLI or by creating cluster secrets.
# Register an external cluster via CLI
argocd cluster add production-us-east \
--name production-us-east \
--kubeconfig ~/.kube/production-us-east.yaml
# List registered clusters
argocd cluster list
# SERVER NAME VERSION STATUS
# https://kubernetes.default.svc in-cluster 1.28 Successful
# https://api.prod-us-east.example.com production-us-east 1.28 Successful
# https://api.prod-eu-west.example.com production-eu-west 1.28 Successful
Cluster Secret (Declarative)
apiVersion: v1
kind: Secret
metadata:
name: production-us-east-cluster
namespace: argocd
labels:
argocd.argoproj.io/secret-type: cluster
env: production
region: us-east
type: Opaque
stringData:
name: production-us-east
server: https://api.prod-us-east.example.com
config: |
{
"bearerToken": "",
"tlsClientConfig": {
"insecure": false,
"caData": ""
}
}
RBAC & Security
Argo CD provides a powerful RBAC system through AppProjects and the built-in policy engine. Projects define boundaries — which repositories, clusters, and resource types an Application can access.
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: team-backend
namespace: argocd
spec:
description: "Backend team project"
# Allowed source repositories
sourceRepos:
- 'https://github.com/myorg/backend-*'
- 'https://github.com/myorg/shared-libs.git'
# Allowed destination clusters and namespaces
destinations:
- server: https://kubernetes.default.svc
namespace: 'backend-*'
- server: https://api.staging.example.com
namespace: 'backend-*'
# Deny-list of cluster resources (prevent privilege escalation)
clusterResourceBlacklist:
- group: ''
kind: Namespace
- group: 'rbac.authorization.k8s.io'
kind: ClusterRole
- group: 'rbac.authorization.k8s.io'
kind: ClusterRoleBinding
# Allowed namespace-scoped resources
namespaceResourceWhitelist:
- group: ''
kind: '*'
- group: 'apps'
kind: '*'
- group: 'networking.k8s.io'
kind: '*'
# Role definitions
roles:
- name: developer
description: "Can sync and view apps"
policies:
- p, proj:team-backend:developer, applications, get, team-backend/*, allow
- p, proj:team-backend:developer, applications, sync, team-backend/*, allow
groups:
- backend-developers # Maps to SSO group
- name: admin
description: "Full access to project"
policies:
- p, proj:team-backend:admin, applications, *, team-backend/*, allow
groups:
- backend-leads
SSO Integration
# argocd-cm ConfigMap — Dex OIDC configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
url: https://argocd.example.com
dex.config: |
connectors:
- type: github
id: github
name: GitHub
config:
clientID: $dex.github.clientID
clientSecret: $dex.github.clientSecret
orgs:
- name: myorg
teams:
- backend-developers
- platform-engineers
- backend-leads
Progressive Delivery with Argo Rollouts
While Argo CD handles deployment synchronization, Argo Rollouts extends Kubernetes with advanced deployment strategies — canary releases, blue-green deployments, and automated analysis-driven promotions.
Canary Deployment
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: api-server
namespace: production
spec:
replicas: 10
revisionHistoryLimit: 3
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
spec:
containers:
- name: api
image: myorg/api-server:v2.1.0
ports:
- containerPort: 8080
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
strategy:
canary:
steps:
- setWeight: 5
- pause: { duration: 2m }
- analysis:
templates:
- templateName: success-rate
args:
- name: service-name
value: api-server
- setWeight: 25
- pause: { duration: 5m }
- analysis:
templates:
- templateName: success-rate
- setWeight: 50
- pause: { duration: 5m }
- setWeight: 100
canaryService: api-server-canary
stableService: api-server-stable
trafficRouting:
nginx:
stableIngress: api-server-ingress
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
namespace: production
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 30s
count: 5
successCondition: result[0] >= 0.95
failureCondition: result[0] < 0.90
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(http_requests_total{service="{{args.service-name}}",
status=~"2.."}[2m])) /
sum(rate(http_requests_total{service="{{args.service-name}}"}[2m]))
Blue-Green Deployment
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: web-frontend
namespace: production
spec:
replicas: 5
selector:
matchLabels:
app: web-frontend
template:
metadata:
labels:
app: web-frontend
spec:
containers:
- name: frontend
image: myorg/web-frontend:v3.0.0
ports:
- containerPort: 3000
strategy:
blueGreen:
activeService: web-frontend-active
previewService: web-frontend-preview
autoPromotionEnabled: false
scaleDownDelaySeconds: 300
prePromotionAnalysis:
templates:
- templateName: smoke-tests
args:
- name: preview-url
value: http://web-frontend-preview.production.svc
Managing & Deleting Applications
App-of-Apps Pattern
The app-of-apps pattern uses a parent Application to manage child Applications, enabling bootstrapping of entire platforms from a single root Application.
# Root Application (manages all other apps)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: platform-root
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/platform-gitops.git
targetRevision: main
path: apps # Contains Application manifests for each component
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
selfHeal: true
prune: true
# Directory structure for app-of-apps
# platform-gitops/
# ├── apps/
# │ ├── cert-manager.yaml # Application CR for cert-manager
# │ ├── external-dns.yaml # Application CR for external-dns
# │ ├── ingress-nginx.yaml # Application CR for ingress-nginx
# │ ├── monitoring.yaml # Application CR for prometheus stack
# │ └── api-server.yaml # Application CR for our API
# └── components/
# ├── cert-manager/
# │ └── kustomization.yaml
# ├── external-dns/
# │ └── values.yaml
# └── monitoring/
# └── kustomization.yaml
Deleting Applications
# Delete app AND its managed resources (cascade delete via finalizer)
argocd app delete guestbook --cascade
# Delete app but KEEP resources in cluster (remove finalizer first)
kubectl -n argocd patch app guestbook \
--type json \
-p '[{"op": "remove", "path": "/metadata/finalizers"}]'
kubectl -n argocd delete app guestbook
# Force-delete stuck application
argocd app delete stuck-app --cascade --force
Conclusion & What's Next
Argo CD transforms Kubernetes deployment from an imperative, error-prone process into a declarative, self-healing system. By implementing GitOps with Argo CD, you gain:
- Auditability — Every deployment is a Git commit with full history and blame
- Reproducibility — Any environment can be recreated from Git at any point in time
- Drift Prevention — Self-heal ensures live state matches declared state
- Multi-Tenancy — Projects and RBAC enforce team boundaries securely
- Scalability — ApplicationSets manage hundreds of apps with minimal YAML
- Safe Releases — Argo Rollouts enables canary and blue-green with automated analysis
Next in the Series
In Part 7: Continuous Integration with GitHub Actions, we'll build production-grade CI pipelines that test, build container images, update Git manifests, and trigger Argo CD syncs — completing the GitOps feedback loop from code commit to production deployment.