Skip to content

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