Skip to content

Argo CD

Argo CD is a GitOps continuous delivery tool for Kubernetes. It watches Git repositories and continuously reconciles the cluster state toward what's declared in those repositories.

The core GitOps contract: the cluster is always a function of Git. No manual kubectl apply. No undocumented hotfixes. Every change is a commit, every deployment is a merge.

Architecture

flowchart TD
    Git[(Git repository\n source of truth)] --> RepoServer[repo-server\nclones + renders manifests]
    RepoServer --> Controller[application-controller\ndiff + sync logic]
    Controller --> K8s[Kubernetes API\ntarget cluster]
    K8s --> Controller
    User --> APIServer[argocd-server\nUI + CLI + API]
    APIServer --> Controller
    Redis[(Redis\ncache + queue)] --> Controller
    Redis --> APIServer
    Dex[dex\nSSO provider] --> APIServer

application-controller: the reconciliation heart. Compares desired state (from repo-server) with live state (from Kubernetes API). Fires syncs when they drift. Runs as a StatefulSet - each shard owns a partition of Applications.

repo-server: clones repositories, renders manifests (plain YAML, Helm, Kustomize, Jsonnet), and caches results. Stateless and horizontally scalable.

argocd-server: the API and UI gateway. Handles authentication, RBAC, webhook ingestion from Git hosts.

dex: bundled OIDC provider for SSO integration (GitHub, Google, LDAP, SAML).

Core concepts

Application: the fundamental unit. Maps a source (Git repo + path + revision) to a destination (cluster + namespace).

AppProject: groups Applications and enforces constraints - which repos are allowed as sources, which clusters and namespaces are allowed as destinations, which resource kinds can be deployed.

Sync: the act of making the live cluster state match the desired state in Git. Can be manual or automatic.

Application definition

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: api-service
  namespace: argocd
spec:
  project: platform

  source:
    repoURL: https://github.com/myorg/k8s-config
    targetRevision: main
    path: apps/api-service/overlays/production

  destination:
    server: https://kubernetes.default.svc
    namespace: production

  syncPolicy:
    automated:
      prune: true        # delete resources removed from Git
      selfHeal: true     # re-sync if cluster state drifts from Git
    syncOptions:
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground
      - ApplyOutOfSyncOnly=true   # only apply changed resources, not the full set
    retry:
      limit: 3
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

prune: true - without this, resources removed from Git are left orphaned in the cluster. Enable it for fully automated environments; leave it off if you have manually-managed resources that Argo CD should ignore.

selfHeal: true - Argo CD watches the cluster for drift and re-applies the desired state automatically. Useful for enforcing immutability against unauthorized kubectl changes.

AppProject

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: platform
  namespace: argocd
spec:
  description: Platform team workloads
  sourceRepos:
    - https://github.com/myorg/k8s-config
    - https://charts.bitnami.com/bitnami
  destinations:
    - namespace: "production"
      server: https://kubernetes.default.svc
    - namespace: "staging"
      server: https://kubernetes.default.svc
  clusterResourceWhitelist:
    - group: ""
      kind: Namespace
  namespaceResourceBlacklist:
    - group: ""
      kind: ResourceQuota
  roles:
    - name: app-developer
      policies:
        - p, proj:platform:app-developer, applications, sync, platform/*, allow
        - p, proj:platform:app-developer, applications, get, platform/*, allow
      groups:
        - myorg:platform-developers

App of Apps pattern

The App of Apps pattern uses a root Application that manages a directory of other Application manifests. When you add a new service, you commit its Application YAML and Argo CD self-registers it.

k8s-config/
└── apps/
    ├── root-app.yaml
    └── applications/
        ├── api-service.yaml
        ├── worker.yaml
        └── database.yaml
# root-app.yaml  -  applied once manually to bootstrap
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root
  namespace: argocd
spec:
  source:
    repoURL: https://github.com/myorg/k8s-config
    path: apps/applications
    targetRevision: main
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

ApplicationSet

ApplicationSet generates Applications from templates and generators. Use it to manage many clusters or environments without copy-pasting Application YAML.

List generator

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: microservices
  namespace: argocd
spec:
  generators:
    - list:
        elements:
          - service: api
            namespace: production
          - service: worker
            namespace: production
  template:
    metadata:
      name: "{{service}}"
    spec:
      project: platform
      source:
        repoURL: https://github.com/myorg/k8s-config
        path: "apps/{{service}}/overlays/production"
        targetRevision: main
      destination:
        server: https://kubernetes.default.svc
        namespace: "{{namespace}}"
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

Git directory generator

Generate one Application per directory:

generators:
  - git:
      repoURL: https://github.com/myorg/k8s-config
      revision: main
      directories:
        - path: "apps/services/*"

Cluster generator

Deploy the same application to all matching clusters:

generators:
  - clusters:
      selector:
        matchLabels:
          environment: production

Adding a new cluster with the environment: production label automatically triggers a new Application without any manifest changes.

Sync waves and hooks

Sync waves control ordering within a single sync operation. Lower waves deploy first.

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

Typical wave ordering:

  • -2: CRDs, Namespaces
  • -1: RBAC, ServiceAccounts, Secrets (via ExternalSecrets)
  • 0: Deployments, Services (default)
  • 1: post-deployment migration Jobs
  • 2: smoke test Jobs

Hooks run Jobs at defined sync lifecycle points and are garbage-collected by Argo CD after completion:

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

Available hooks: PreSync, Sync, PostSync, SyncFail, Skip.

Multi-cluster management

argocd cluster add production-us-east --name production-us-east
argocd cluster add production-eu-west --name production-eu-west
argocd cluster list

Cluster credentials are stored as Secrets in the argocd namespace. For external clusters, Argo CD uses a dedicated ServiceAccount and short-lived tokens. Use the --service-account flag to specify a pre-created ServiceAccount with scoped RBAC instead of cluster-admin.

RBAC

Argo CD's RBAC uses Casbin policies. Define in argocd-rbac-cm:

p, role:readonly, applications, get, */*, allow
p, role:readonly, projects, get, *, allow

p, role:deployer, applications, sync, platform/*, allow
p, role:deployer, applications, get, platform/*, allow

g, myorg:platform-leads, role:admin
g, myorg:developers, role:deployer

Scope roles to AppProjects using proj:platform:role-name pattern for fine-grained control.

Argo Rollouts

Argo Rollouts extends Argo CD with progressive delivery - canary and blue-green deployments backed by automated analysis:

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: api
spec:
  replicas: 10
  strategy:
    canary:
      steps:
        - setWeight: 10
        - pause: {duration: 5m}
        - analysis:
            templates:
              - templateName: success-rate
        - setWeight: 50
        - pause: {duration: 10m}
        - setWeight: 100
      canaryService: api-canary
      stableService: api-stable
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate
spec:
  metrics:
    - name: success-rate
      successCondition: result[0] >= 0.95
      provider:
        prometheus:
          address: http://prometheus:9090
          query: |
            sum(rate(http_requests_total{job="api",status!~"5.."}[5m]))
            / sum(rate(http_requests_total{job="api"}[5m]))

If the AnalysisRun fails, Rollouts aborts and rolls back to stable automatically. The Rollout replaces Deployment - manage it through Argo CD like any other resource.

Production patterns

Separate config from code: application code repo triggers CI, which builds and pushes the image, then opens a PR against the ops config repo to update the image tag. Argo CD deploys on merge. The audit trail lives in Git, not CI logs.

Protect main: selfHeal: true enforces Git as the source of truth. Combined with branch protection on main, it means no one can make lasting cluster changes via kubectl.

Image updater: the Argo CD Image Updater watches container registries and automatically commits image tag updates to Git when new images are pushed. Useful for CD pipelines where you don't want CI to directly touch the config repo.

Notification controller: send Slack or PagerDuty alerts on sync failure, health degradation, or app creation. Configure via ConfigMap argocd-notifications-cm.

Drift detection: argocd app diff <app> shows what would change if you sync. Use argocd app list --sync-status OutOfSync to catch drift across all applications.