ArgoCD & GitOps Reference
ArgoCD & GitOps Reference
ArgoCD core concepts, sync strategies, ApplicationSets, RBAC, and the production patterns that prevent 2am incidents — covering ArgoCD 2.x and GitOps principles with Flux 2 comparisons.
Core concepts and CLI
# Install ArgoCD CLI
brew install argocd # macOS
curl -sSL -o argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
chmod +x argocd && mv argocd /usr/local/bin/
# Login
argocd login argocd.example.com
argocd login argocd.example.com --sso # SSO (Dex/OIDC)
argocd login localhost:8080 --insecure # local dev
# Port-forward to UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Initial admin password (delete secret after changing password)
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d
# App lifecycle
argocd app list
argocd app get my-app # status, diff, events
argocd app sync my-app # trigger sync
argocd app sync my-app --prune # delete resources not in git
argocd app sync my-app --dry-run # preview what would change
argocd app wait my-app --health # block until healthy
argocd app diff my-app # git vs cluster diff
argocd app rollback my-app 3 # rollback to history revision 3
argocd app history my-app # list sync history
# Manage clusters
argocd cluster add my-cluster # add from current kubeconfig
argocd cluster list
argocd cluster get my-cluster
Application manifest — all the fields
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/my-org/my-app
targetRevision: HEAD # branch, tag, or commit SHA
path: kubernetes/overlays/production
# Helm chart:
# chart: my-chart
# repoURL: https://charts.example.com
# targetRevision: "1.2.3"
# helm:
# valueFiles: [values.yaml, values-prod.yaml]
# parameters:
# - name: image.tag
# value: "v1.4.0"
# Kustomize overlay (auto-detected if kustomization.yaml present):
kustomize:
images:
- my-org/my-app:v1.4.0 # image override without modifying git
destination:
server: https://kubernetes.default.svc # in-cluster
namespace: my-app
syncPolicy:
automated:
prune: true # delete resources removed from git
selfHeal: true # re-sync if cluster drifts from git
syncOptions:
- CreateNamespace=true # create namespace if not exists
- PrunePropagationPolicy=foreground # wait for pods to die before deleting
- RespectIgnoreDifferences=true
retry:
limit: 3
backoff:
duration: 5s
maxDuration: 3m
factor: 2
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas # ignore HPA-managed replica count
- group: ""
kind: Secret
jsonPointers:
- /data # ignore secrets managed by external-secrets
Sync strategies and waves
# Sync waves — control apply ORDER within a sync operation
# Lower wave number = applied first
# ArgoCD waits for wave N to be Healthy before applying wave N+1
# Database migrations first, then app deployment
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrations
annotations:
argocd.argoproj.io/hook: PreSync # runs BEFORE sync
argocd.argoproj.io/hook-delete-policy: HookSucceeded # clean up on success
spec:
template:
spec:
containers:
- name: migrate
image: my-app:v1.4.0
command: ["python", "manage.py", "migrate"]
restartPolicy: Never
---
# Apply resources in order:
metadata:
annotations:
argocd.argoproj.io/sync-wave: "1" # namespace + RBAC first
# Then wave 2: configmaps + secrets
# Then wave 3: deployments
# Then wave 4: ingress
# Hooks:
# PreSync → before sync (migrations, backup)
# Sync → during sync (parallel with other resources)
# PostSync → after all resources are Healthy (smoke tests, notifications)
# SyncFail → if sync fails (alert, rollback trigger)
# Hook delete policies:
# HookSucceeded → delete Job when it succeeds
# HookFailed → delete Job when it fails
# BeforeHookCreation → delete previous run before creating (default-ish)
# Sync options on a resource (not just the App):
metadata:
annotations:
argocd.argoproj.io/sync-options: Prune=false # never prune this resource
argocd.argoproj.io/compare-options: IgnoreExtraneous # ignore if not in git
ApplicationSet — multi-cluster and multi-environment
# ApplicationSet generates multiple Applications from a template
# Most common use: deploy same app to all clusters / all environments
# Generator 1: list generator (explicit)
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-app-environments
namespace: argocd
spec:
generators:
- list:
elements:
- env: staging
cluster: staging-cluster
url: https://staging-api.example.com
- env: production
cluster: production-cluster
url: https://prod-api.example.com
template:
metadata:
name: "my-app-{{env}}"
spec:
source:
repoURL: https://github.com/my-org/my-app
path: "kubernetes/overlays/{{env}}"
targetRevision: HEAD
destination:
server: "{{url}}"
namespace: my-app
---
# Generator 2: cluster generator (all registered clusters)
generators:
- clusters:
selector:
matchLabels:
environment: production # only clusters with this label
---
# Generator 3: git generator (directory per environment in repo)
generators:
- git:
repoURL: https://github.com/my-org/deployments
revision: HEAD
directories:
- path: apps/* # apps/frontend, apps/backend, apps/worker
files:
- path: apps/*/config.json # or files with JSON config per app
---
# Generator 4: pull request generator (deploy branch for each open PR)
generators:
- pullRequest:
github:
owner: my-org
repo: my-app
tokenRef:
secretName: github-token
key: token
requeueAfterSeconds: 180 # check for new PRs every 3 minutes
template:
metadata:
name: "preview-{{branch_slug}}"
spec:
destination:
namespace: "preview-{{branch_slug}}"
RBAC and SSO
# ArgoCD RBAC is configured in the argocd-rbac-cm ConfigMap
# argocd-rbac-cm
data:
policy.default: role:readonly # everyone is readonly by default
policy.csv: |
# Admins can do everything
p, role:admin, applications, *, */*, allow
p, role:admin, clusters, *, *, allow
p, role:admin, repositories, *, *, allow
# Developers can sync their own app project
p, role:developer, applications, sync, my-project/*, allow
p, role:developer, applications, get, my-project/*, allow
# Bind SSO group to role
g, my-org:platform-team, role:admin
g, my-org:developers, role:developer
# Scope to a project (AppProject)
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: my-project
spec:
sourceRepos:
- "https://github.com/my-org/*" # allowed source repos
destinations:
- server: https://kubernetes.default.svc
namespace: my-app-* # allowed target namespaces
clusterResourceWhitelist:
- group: ""
kind: Namespace
namespaceResourceBlacklist:
- group: ""
kind: ResourceQuota # prevent creating ResourceQuotas
# SSO with GitHub
# In argocd-cm:
data:
url: https://argocd.example.com
dex.config: |
connectors:
- type: github
id: github
name: GitHub
config:
clientID: YOUR_CLIENT_ID
clientSecret: $dex.github.clientSecret
orgs:
- name: my-org
ArgoCD vs Flux 2 — which to choose
Both implement GitOps. The choice depends on your team’s preferences.
# ArgoCD
# ✅ Web UI — great for visibility and onboarding new team members
# ✅ Multi-cluster management from a single control plane
# ✅ ApplicationSet for matrix deployments
# ✅ Rollback via UI/CLI to any sync history revision
# ✅ SSO + RBAC built-in
# ⚠️ Runs as a deployment in argocd namespace (attack surface)
# ⚠️ argocd CLI required for most operations
# Flux 2
# ✅ Pure GitOps — no UI, no CLI state (everything is a CRD)
# ✅ Lightweight — just controllers watching CRDs
# ✅ Native Kustomize + Helm controller
# ✅ Notification controller (alerts to Slack/Teams/PagerDuty)
# ✅ Image automation (auto-PR when new image tag available)
# ⚠️ No UI (Weave GitOps adds one, separate project)
# ⚠️ Steeper learning curve — more CRDs (GitRepository, Kustomization, HelmRelease...)
# Rule of thumb:
# Team of 1-5, K8s-native culture → Flux 2
# Multiple teams, visibility matters, multi-cluster → ArgoCD
# Flux 2 equivalents for common ArgoCD concepts:
# ArgoCD Application → Flux Kustomization or HelmRelease
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata: { name: my-app, namespace: flux-system }
spec:
interval: 10m
sourceRef: { kind: GitRepository, name: my-repo }
path: ./kubernetes/overlays/production
prune: true # equivalent to argocd prune
force: false # equivalent to argocd self-heal (re-apply)
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: my-app
namespace: my-app
# Flux watch source
flux get sources git
flux get kustomizations
flux reconcile kustomization my-app --with-source # force sync
flux logs --follow --kind=Kustomization # troubleshoot
Notifications and webhooks
# ArgoCD Notifications (installed separately or bundled in newer versions)
# Triggers: app sync, health change, deploy success/failure
# argocd-notifications-cm (triggers + templates)
data:
trigger.on-sync-failed: |
- when: app.status.operationState.phase in ['Error', 'Failed']
send: [app-sync-failed]
trigger.on-deployed: |
- when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status == 'Healthy'
send: [app-deployed]
template.app-sync-failed: |
slack:
attachments: |
[{
"title": "{{.app.metadata.name}} sync failed",
"color": "#E96D76",
"fields": [{
"title": "Error",
"value": "{{.app.status.operationState.message}}"
}]
}]
template.app-deployed: |
slack:
attachments: |
[{
"title": "{{.app.metadata.name}} deployed successfully",
"color": "#18be52",
"fields": [{
"title": "Revision",
"value": "{{.app.status.sync.revision}}"
}]
}]
# Subscribe an app to notifications:
metadata:
annotations:
notifications.argoproj.io/subscribe.on-sync-failed.slack: my-channel
notifications.argoproj.io/subscribe.on-deployed.slack: deployments
# GitHub deployment status integration
notifications.argoproj.io/subscribe.on-deployed.github: ""
Troubleshooting
# App stuck in Progressing or OutOfSync
argocd app diff my-app # what's different?
argocd app sync my-app --force # force overwrite even if cluster has changes
# Sync failed — check the operation state
argocd app get my-app -o json | jq '.status.operationState'
# Resources the app manages
argocd app resources my-app
argocd app resource-actions list my-app --resource-name my-pod
# ArgoCD itself is broken?
kubectl -n argocd get pods
kubectl -n argocd logs deploy/argocd-server # API server logs
kubectl -n argocd logs deploy/argocd-application-controller # sync controller logs
kubectl -n argocd logs deploy/argocd-repo-server # git/helm fetching logs
# Repo unreachable
kubectl -n argocd exec deploy/argocd-repo-server -- \
git ls-remote https://github.com/my-org/my-app
# Check what ArgoCD sees for a specific revision
argocd app manifests my-app --revision HEAD
argocd app manifests my-app --revision abc1234 # specific commit
# Resource is excluded from sync (ignoreDifferences working?)
# Check: Settings → Repositories → repo → connection status
# Check: App → Events tab for sync errors
# Common OutOfSync causes:
# 1. Kustomize image override not matching deployed image
# 2. HPA changed replicas (use ignoreDifferences for /spec/replicas)
# 3. Secret manager injected annotations (use ignoreDifferences for /metadata/annotations)
# 4. Controller mutating the resource (admission webhook added fields)
Track Kubernetes EOL dates, version history, and upgrade paths at ReleaseRun Kubernetes Releases — free, live data.
🔍 Free tool: K8s YAML Security Linter — check the K8s manifests ArgoCD manages for security misconfigurations before they reach production.
Founded
2023 in London, UK
Contact
hello@releaserun.com