Andrew Mercer
on this page

ArgoCD: A Comprehensive Guide

1. What ArgoCD Is

ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes. It runs as a set of controllers inside a cluster, continuously compares the desired state (manifests in a Git repo, Helm chart, Kustomize overlay, or plain directory) against the live state of the cluster, and reconciles drift — either automatically or on approval.

Core GitOps principles it enforces: - Git is the single source of truth for what should be running. - Declarative — you describe end state, not imperative steps. - Pulled, not pushed — the cluster pulls changes from Git rather than CI pushing kubectl apply. - Continuously reconciled — drift is detected and (optionally) auto-corrected.

ArgoCD was originally built by Intuit, is now a CNCF graduated project, and is one of the two dominant GitOps engines alongside Flux.

2. Architecture

ArgoCD is composed of several controllers/services, typically deployed via Helm chart or the official install manifests into an argocd namespace:

Component Role
API Server gRPC/REST server backing the UI, CLI, and webhook receiver. Handles auth, RBAC enforcement, and application management operations.
Repository Server Clones/caches Git repos and Helm chart repos, renders manifests (via helm template, kustomize build, plain YAML, or a config management plugin), and returns rendered manifests.
Application Controller The reconciliation loop. Watches Application CRs, diffs live vs. desired state using the K8s API, and triggers sync operations.
ApplicationSet Controller Generates Application resources dynamically from generators (list, cluster, git directory/file, matrix, SCM provider, pull request, etc.) — this is how you template many Applications from one spec.
Notifications Controller Sends alerts (Slack, email, webhook, etc.) on sync/health state transitions, defined via subscriptions and triggers.
Dex (optional) OIDC/SSO connector bundled for integrating external identity providers (GitHub, GitLab, Azure AD/Entra ID, LDAP).
Redis Caching layer for the repo server and API server.

All of this state is expressed as Kubernetes Custom Resources (Application, AppProject, ApplicationSet), so ArgoCD itself is "just" a set of controllers reconciling CRDs — consistent with the Kubernetes operator pattern.

3. Installation

Standard non-HA install:

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

For production, use the HA manifests (ha/install.yaml) or the Helm chart (argo/argo-cd), which lets you tune replica counts, resource limits, and Redis HA (Sentinel) via values.yaml. Helm is the more Terraform/GitOps-friendly path since you can pin chart versions and manage config as code.

Access the UI initially via port-forward:

kubectl port-forward svc/argocd-server -n argocd 8080:443

Initial admin password is auto-generated in the argocd-initial-admin-secret.

4. Core Concepts

4.1 The Application CRD

The fundamental unit. An Application binds a source (Git repo path, Helm chart, or OCI artifact) to a destination (cluster + namespace):

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-service
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/org/repo.git
    targetRevision: main
    path: k8s/my-service
  destination:
    server: https://kubernetes.default.svc
    namespace: my-service
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
  • prune: true — deletes resources removed from Git.
  • selfHeal: true — reverts manual/out-of-band cluster changes back to Git state.
  • Without automated, sync is manual (via UI/CLI/API) — useful for higher environments where you want a gate.

4.2 AppProject

A logical grouping/RBAC boundary. Projects restrict: - Which repos an Application can source from (sourceRepos). - Which clusters/namespaces it can deploy to (destinations). - Which resource kinds are allowed (clusterResourceWhitelist, namespaceResourceBlacklist). - Role bindings for fine-grained RBAC per team.

This is the primary multi-tenancy mechanism — e.g., a platform-team project can deploy CRDs and cluster-scoped resources, while a dev-team project is locked to namespace-scoped resources in specific namespaces.

4.3 Sync, Health, and Diffing

ArgoCD computes a live diff using server-side dry-run/diff logic. Resource health is assessed via built-in health checks (Deployment rollout status, Job completion, PVC bound, etc.) or custom Lua health checks for CRDs that don't have a built-in assessor (common for Argo Rollouts, cert-manager Certificates, etc.).

Sync status: Synced / OutOfSync. Health status: Healthy / Progressing / Degraded / Suspended / Missing / Unknown.

4.4 Sync Waves and Hooks

For ordered rollout of interdependent resources (e.g., CRDs before CRs, namespace before workloads, migration Job before Deployment):

metadata:
  annotations:
    argocd.argoproj.io/sync-wave: "-1"

Lower waves sync first. Combined with hooks (PreSync, Sync, PostSync, SyncFail) for one-shot Jobs like DB migrations:

metadata:
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: HookSucceeded

4.5 App of Apps Pattern

A parent Application whose source directory contains child Application manifests. This lets you bootstrap an entire cluster's Application set from a single root Application, and manage the app catalog itself as GitOps.

4.6 ApplicationSets

The scalable answer to "one Application per environment/cluster/tenant." Generators produce a matrix of parameters that template into many Applications from one ApplicationSet spec. Common generators:

  • List — static enumerated values.
  • Cluster — one Application per registered cluster (great for fleet rollout of a platform component).
  • Git directory/file — one Application per matching path in a repo (self-service: teams add a directory, get an Application).
  • Matrix — cartesian product of two generators (e.g., clusters × environments).
  • SCM Provider / Pull Request — dynamically generate Applications per repo or per open PR (ephemeral preview environments).
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: services
spec:
  generators:
    - git:
        repoURL: https://github.com/org/repo.git
        revision: main
        directories:
          - path: apps/*
  template:
    metadata:
      name: '{{path.basename}}'
    spec:
      source:
        repoURL: https://github.com/org/repo.git
        targetRevision: main
        path: '{{path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{path.basename}}'

5. Rendering Engines Supported

  • Plain YAML directories
  • Helm (native support — values files, value overrides in the Application spec, Helm hooks translated to sync hooks)
  • Kustomize (native — overlays, patches, images, replicas overrides directly in the spec)
  • Jsonnet
  • Custom Config Management Plugins (CMPs) for anything else (cdk8s, custom templating, helmfile, etc.), run as sidecars to the repo-server pod.

6. RBAC and SSO

  • Local argocd-rbac-cm ConfigMap defines policy in Casbin syntax: p, role:dev, applications, sync, project-name/*, allow.
  • Groups from OIDC claims (via Dex or native OIDC) map to roles — typical pattern is to bind IdP groups (GitHub teams, Azure AD groups) directly rather than managing local users.
  • AppProject-scoped roles allow project-level self-service RBAC delegated to team leads.

7. Multi-Cluster Management

ArgoCD is commonly run as a hub: one management cluster running ArgoCD, registered against N spoke (workload) clusters via argocd cluster add (which creates a Secret holding the target cluster's kubeconfig/service account token). This is ArgoCD's most distinctive architectural strength versus Flux — a single control plane with a UI giving fleet-wide visibility.

Alternative: run ArgoCD per-cluster ("in-cluster only") for stronger blast-radius isolation, at the cost of losing the single-pane-of-glass view (mitigated somewhat by tools like Argo CD Notifications + a shared dashboard, or the newer ApplicationSet cluster generator for fleet-wide app distribution).

8. CLI Essentials

argocd login <server>
argocd app list
argocd app get my-service
argocd app sync my-service
argocd app diff my-service
argocd app history my-service
argocd app rollback my-service <revision-id>
argocd repo add https://github.com/org/repo.git --username x --password $TOKEN
argocd cluster add <kube-context-name>

9. Notifications

Declarative subscriptions/triggers via annotations or the argocd-notifications-cm/-secret ConfigMaps:

metadata:
  annotations:
    notifications.argoproj.io/subscribe.on-sync-succeeded.slack: platform-alerts

Supports Slack, Teams, email, generic webhooks, PagerDuty, and templated messages referencing the Application object.

10. Progressive Delivery

ArgoCD pairs naturally with Argo Rollouts (a separate but related Argo-project CRD/controller) for canary and blue/green deployments with automated analysis (Prometheus/Datadog metric queries gating promotion). ArgoCD deploys the Rollout object; Argo Rollouts drives the progressive traffic shift. The ArgoCD UI has native Rollout visualization built in.

11. Operational Best Practices

  • Use selfHeal cautiously in shared/dev clusters — it will fight anyone doing kubectl edit for a live debug session; either disable it there or clearly communicate the policy.
  • Separate config repos from app source repos — decouples app CI (build/test/push image) from CD (bump manifest/tag), often via a bot commit or Image Updater.
  • Use ArgoCD Image Updater (separate controller) if you want automated image-tag bumps written back to Git, keeping the "Git is truth" guarantee intact rather than mutating live state out-of-band.
  • Pin targetRevision to tags/SHAs in production, branches in lower environments.
  • Use Projects aggressively for blast-radius control, even in single-tenant clusters — it's cheap insurance.
  • Resource exclusions (resource.exclusions in argocd-cm) to keep the controller from watching noisy, high-churn resources it doesn't need to (e.g., Endpoints, EndpointSlices) — meaningfully reduces controller CPU/memory at scale.
  • Sharding: at fleet scale (100s of clusters/Applications), enable Application Controller sharding (ARGOCD_CONTROLLER_REPLICAS + shard annotations) to horizontally scale reconciliation.

12. Troubleshooting

Symptom Likely Cause
App stuck Progressing Health check never resolves — check for a bad readiness probe, or a CRD lacking a custom Lua health check (defaults to Healthy immediately, or Progressing forever depending on config).
OutOfSync immediately after sync Diff normalization issue — a mutating webhook or defaulting controller is rewriting fields post-apply. Add an ignoreDifferences rule.
Repo server OOMKilled Large Helm charts/monorepos on every refresh; increase repo-server resources or enable manifest caching / shallow clones.
Sync hangs on hook PreSync Job never completes — check hook Job logs directly with kubectl logs, ArgoCD just reports the wait.
Cluster credentials rejected Rotated service account token/kubeconfig not updated via argocd cluster add --upsert.

13. Where ArgoCD Excels

  • Strong UI/UX and fleet-wide visibility for platform teams supporting many app teams.
  • Native, opinionated multi-cluster hub model.
  • Rich RBAC/multi-tenancy primitives out of the box (Projects).
  • ApplicationSets for large-scale, self-service app onboarding.
  • Tight integration with Argo Rollouts for progressive delivery.