How to Implement GitOps with Argo CD
Deploy and manage Kubernetes applications declaratively with Argo CD GitOps. Learn application deployment, sync strategies, multi-cluster management, and best practices.
The Problem
Manual kubectl applies are error-prone, hard to track, and donβt scale. You need automated, auditable, and reversible deployments with Git as the single source of truth.
The Solution
Implement GitOps with Argo CD to automatically sync Kubernetes cluster state with Git repositories, providing declarative, version-controlled deployments with automatic drift detection and remediation.
GitOps Principles
flowchart TB
subgraph GIT["π GIT REPOSITORY - Single Source of Truth"]
direction TB
APPS["π apps/"]
PROD["βββ production/<br/>β βββ deployment.yaml<br/>β βββ service.yaml<br/>β βββ kustomization.yaml"]
STAGE["βββ staging/<br/> βββ ..."]
APPS --- PROD
APPS --- STAGE
end
DEV["π¨βπ» Developer"] -->|"1οΈβ£ Git push"| GIT
GIT -->|"2οΈβ£ Detect changes"| ARGO
subgraph ARGO["πΆ ARGO CD"]
direction TB
CTRL["ποΈ Application Controller"]
FEATURES["β’ Watches Git repositories<br/>β’ Compares desired vs actual state<br/>β’ Syncs automatically or on-demand<br/>β’ Detects and reports drift"]
CTRL --- FEATURES
end
ARGO -->|"3οΈβ£ Apply changes"| K8S
K8S -.->|"4οΈβ£ Git commit (auto-sync)"| GIT
subgraph K8S["βΈοΈ KUBERNETES CLUSTER"]
RESOURCES["π¦ Deployed Resources<br/>(Always matches Git state)"]
endStep 1: Install Argo CD
Using kubectl
# Create namespace
kubectl create namespace argocd
# Install Argo CD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Wait for pods to be ready
kubectl wait --for=condition=Ready pods --all -n argocd --timeout=300sUsing Helm
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argocd argo/argo-cd \
--namespace argocd \
--create-namespace \
--set server.service.type=LoadBalancerAccess Argo CD UI
# Port forward
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
# Login via CLI
argocd login localhost:8080 --username admin --password <password> --insecure
# Change password
argocd account update-passwordStep 2: Connect Git Repository
Via CLI
# Add repository with HTTPS
argocd repo add https://github.com/myorg/k8s-configs.git \
--username <username> \
--password <token>
# Add repository with SSH
argocd repo add git@github.com:myorg/k8s-configs.git \
--ssh-private-key-path ~/.ssh/id_rsaVia Kubernetes Secret
apiVersion: v1
kind: Secret
metadata:
name: private-repo
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
stringData:
type: git
url: https://github.com/myorg/k8s-configs.git
username: git
password: <github-token>Step 3: Create Applications
Simple Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-configs.git
targetRevision: main
path: apps/myapp/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=trueApplication with Helm
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: prometheus
namespace: argocd
spec:
project: default
source:
repoURL: https://prometheus-community.github.io/helm-charts
chart: kube-prometheus-stack
targetRevision: 55.5.0
helm:
releaseName: prometheus
values: |
grafana:
enabled: true
adminPassword: admin123
prometheus:
prometheusSpec:
retention: 7d
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: standard
resources:
requests:
storage: 50Gi
destination:
server: https://kubernetes.default.svc
namespace: monitoring
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=trueApplication with Kustomize
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-production
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-configs.git
targetRevision: main
path: apps/myapp/overlays/production
kustomize:
images:
- myapp=myregistry/myapp:v1.2.3
destination:
server: https://kubernetes.default.svc
namespace: productionStep 4: App of Apps Pattern
Root Application
# apps/root-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root-app
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-configs.git
targetRevision: main
path: apps
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: trueChild Applications
# apps/myapp.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-configs.git
targetRevision: main
path: manifests/myapp
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
---
# apps/monitoring.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: monitoring
namespace: argocd
spec:
project: default
source:
repoURL: https://prometheus-community.github.io/helm-charts
chart: kube-prometheus-stack
targetRevision: 55.5.0
destination:
server: https://kubernetes.default.svc
namespace: monitoringStep 5: ApplicationSet for Multi-Environment
Generator: List
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: myapp-environments
namespace: argocd
spec:
generators:
- list:
elements:
- env: staging
namespace: staging
replicas: "1"
- env: production
namespace: production
replicas: "3"
template:
metadata:
name: myapp-{{env}}
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-configs.git
targetRevision: main
path: apps/myapp/overlays/{{env}}
kustomize:
commonAnnotations:
environment: "{{env}}"
destination:
server: https://kubernetes.default.svc
namespace: "{{namespace}}"
syncPolicy:
automated:
prune: true
selfHeal: trueGenerator: Git Directory
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: cluster-addons
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/myorg/k8s-configs.git
revision: main
directories:
- path: cluster-addons/*
template:
metadata:
name: "{{path.basename}}"
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-configs.git
targetRevision: main
path: "{{path}}"
destination:
server: https://kubernetes.default.svc
namespace: "{{path.basename}}"
syncPolicy:
automated:
selfHeal: trueGenerator: Cluster
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: myapp-multi-cluster
namespace: argocd
spec:
generators:
- clusters:
selector:
matchLabels:
environment: production
template:
metadata:
name: myapp-{{name}}
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-configs.git
targetRevision: main
path: apps/myapp
destination:
server: "{{server}}"
namespace: productionStep 6: Sync Strategies
Manual Sync (Default)
spec:
syncPolicy: {} # No automated syncAutomated Sync
spec:
syncPolicy:
automated:
prune: true # Delete resources removed from Git
selfHeal: true # Revert manual changes
allowEmpty: falseSync Options
spec:
syncPolicy:
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
- PruneLast=true
- RespectIgnoreDifferences=true
- ApplyOutOfSyncOnly=true
- ServerSideApply=trueSync Waves (Ordering)
# Deploy in order: namespace β configmap β deployment β service
apiVersion: v1
kind: Namespace
metadata:
name: myapp
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
annotations:
argocd.argoproj.io/sync-wave: "0"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
annotations:
argocd.argoproj.io/sync-wave: "1"
---
apiVersion: v1
kind: Service
metadata:
name: myapp
annotations:
argocd.argoproj.io/sync-wave: "2"Sync Hooks
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
template:
spec:
containers:
- name: migrate
image: myapp:latest
command: ["./migrate.sh"]
restartPolicy: Never
---
apiVersion: batch/v1
kind: Job
metadata:
name: smoke-test
annotations:
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
template:
spec:
containers:
- name: test
image: myapp:latest
command: ["./smoke-test.sh"]
restartPolicy: NeverStep 7: Projects and RBAC
Create AppProject
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: production
namespace: argocd
spec:
description: Production applications
sourceRepos:
- https://github.com/myorg/k8s-configs.git
- https://charts.helm.sh/stable
destinations:
- namespace: production
server: https://kubernetes.default.svc
- namespace: production-*
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: ""
kind: Namespace
namespaceResourceWhitelist:
- group: "*"
kind: "*"
roles:
- name: developer
description: Developer access
policies:
- p, proj:production:developer, applications, get, production/*, allow
- p, proj:production:developer, applications, sync, production/*, allow
groups:
- developersRBAC Configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
namespace: argocd
data:
policy.default: role:readonly
policy.csv: |
# Admins can do anything
p, role:admin, applications, *, */*, allow
p, role:admin, clusters, *, *, allow
p, role:admin, repositories, *, *, allow
p, role:admin, projects, *, *, allow
# Developers can sync and view
p, role:developer, applications, get, */*, allow
p, role:developer, applications, sync, */*, allow
p, role:developer, applications, action/*, */*, allow
p, role:developer, logs, get, */*, allow
# Map groups to roles
g, admins, role:admin
g, developers, role:developerStep 8: Multi-Cluster Management
Add External Cluster
# Add cluster via CLI
argocd cluster add <context-name> --name production-cluster
# Or via Secret
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: production-cluster
namespace: argocd
labels:
argocd.argoproj.io/secret-type: cluster
type: Opaque
stringData:
name: production-cluster
server: https://production.k8s.example.com
config: |
{
"bearerToken": "<service-account-token>",
"tlsClientConfig": {
"insecure": false,
"caData": "<base64-ca-cert>"
}
}
EOFDeploy to Multiple Clusters
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: multi-cluster-app
namespace: argocd
spec:
generators:
- clusters: {} # All clusters
template:
metadata:
name: myapp-{{name}}
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-configs.git
path: apps/myapp
targetRevision: main
destination:
server: "{{server}}"
namespace: productionMonitoring and Notifications
Slack Notifications
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: argocd
data:
service.slack: |
token: $slack-token
template.app-sync-status: |
message: |
Application {{.app.metadata.name}} sync status: {{.app.status.sync.status}}
Health: {{.app.status.health.status}}
trigger.on-sync-status-unknown: |
- when: app.status.sync.status == 'Unknown'
send: [app-sync-status]
trigger.on-health-degraded: |
- when: app.status.health.status == 'Degraded'
send: [app-sync-status]Application Annotations for Notifications
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
annotations:
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: deployments
notifications.argoproj.io/subscribe.on-sync-failed.slack: deploymentsVerification Commands
# List applications
argocd app list
# Get application details
argocd app get myapp
# Sync application
argocd app sync myapp
# View application history
argocd app history myapp
# Rollback to previous version
argocd app rollback myapp <revision>
# Check diff before sync
argocd app diff myapp
# Watch application sync
argocd app wait myapp --syncBest Practices
1. Repository Structure
k8s-configs/
βββ apps/ # Application definitions
β βββ myapp/
β β βββ base/
β β βββ overlays/
β β βββ staging/
β β βββ production/
βββ cluster-addons/ # Cluster-wide components
β βββ cert-manager/
β βββ ingress-nginx/
β βββ monitoring/
βββ argocd/ # Argo CD configuration
βββ applications/
βββ projects/2. Use Sealed Secrets for Sensitive Data
# Never commit plain secrets to Git
kubeseal < secret.yaml > sealed-secret.yaml3. Implement Progressive Delivery
# Use sync waves and hooks for safe deployments
annotations:
argocd.argoproj.io/sync-wave: "1"
argocd.argoproj.io/hook: PreSyncSummary
Argo CD enables GitOps workflows by keeping Kubernetes clusters in sync with Git repositories. Use ApplicationSets for multi-environment deployments, sync waves for ordering, and projects for access control. The declarative approach provides auditable, reversible, and automated deployments.
π Go Further with Kubernetes Recipes
Love this recipe? Thereβs so much more! This is just one of 100+ hands-on recipes in our comprehensive Kubernetes Recipes book.
Inside the book, youβll master:
- β Production-ready deployment strategies
- β Advanced networking and security patterns
- β Observability, monitoring, and troubleshooting
- β Real-world best practices from industry experts
βThe practical, recipe-based approach made complex Kubernetes concepts finally click for me.β
π Get Your Copy Now β Start building production-grade Kubernetes skills today!
π Get All 100+ Recipes in One Book
Stop searching β get every production-ready pattern with detailed explanations, best practices, and copy-paste YAML.
Want More Kubernetes Recipes?
This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.