Argo CD ApplicationSet: Multi-Cluster Deployment and Generator Patterns
A single Argo CD Application deploys one application to one cluster. ApplicationSet generates multiple Applications from a template — one per cluster, one per Git directory, one per team. It's the mechanism that turns Argo CD from a single-app deployment tool into a fleet deployment platform.

Argo CD's Application resource deploys one Helm chart or Kustomize directory to one target cluster. This works for a handful of applications, but doesn't scale to platform engineering use cases: deploying the same application to 20 clusters, deploying every subdirectory in a monorepo as a separate application, or onboarding a new team by adding a directory to Git and having Argo CD automatically deploy everything in it.
ApplicationSet solves this by templating Application creation. You define one ApplicationSet with a generator (which produces a list of parameter sets) and a template (which defines one Application per parameter set). The ApplicationSet controller creates, updates, and deletes Application resources automatically as the generator's output changes.
How ApplicationSet Works
ApplicationSet → Generator → [param set 1, param set 2, ...] → Application per param set
The generator queries a data source (cluster registry, Git repository, list) and produces a list of parameter sets. Each parameter set is used to render the template, producing one Application object. When the data source changes (a new cluster is added, a new directory appears in Git), the ApplicationSet controller reconciles the Application list.
ApplicationSet is bundled with Argo CD since v2.3 and enabled by default — no separate installation needed. Before v2.3, ApplicationSet required a separate kubectl apply of the applicationset-controller manifest.
List Generator
The simplest generator: an explicit list of environments or clusters.
1apiVersion: argoproj.io/v1alpha1
2kind: ApplicationSet
3metadata:
4 name: my-app
5 namespace: argocd
6spec:
7 generators:
8 - list:
9 elements:
10 - cluster: staging
11 url: https://k8s-staging.example.com
12 values:
13 replicas: "1"
14 resourceSize: small
15 - cluster: production
16 url: https://k8s-prod.example.com
17 values:
18 replicas: "3"
19 resourceSize: large
20 template:
21 metadata:
22 name: "my-app-{{cluster}}"
23 spec:
24 project: default
25 source:
26 repoURL: https://github.com/my-org/fleet-apps
27 targetRevision: HEAD
28 path: apps/my-app
29 helm:
30 values: |
31 replicaCount: {{values.replicas}}
32 resources:
33 preset: {{values.resourceSize}}
34 destination:
35 server: "{{url}}"
36 namespace: my-app
37 syncPolicy:
38 automated:
39 prune: true
40 selfHeal: true
41 syncOptions:
42 - CreateNamespace=trueThe List generator is appropriate when the set of targets is small and changes rarely. For dynamic fleets, the Cluster generator is better.
Cluster Generator
Generates one Application per cluster registered in Argo CD's cluster store. As you register new clusters, ApplicationSet automatically deploys to them:
1spec:
2 generators:
3 - clusters:
4 selector:
5 matchLabels:
6 environment: production # Only production clusters
7 values:
8 region: "{{metadata.annotations.region}}" # Use cluster annotations
9 template:
10 metadata:
11 name: "monitoring-{{name}}" # {{name}} = cluster name in Argo CD
12 spec:
13 source:
14 repoURL: https://github.com/my-org/fleet
15 path: platform/monitoring
16 targetRevision: HEAD
17 destination:
18 server: "{{server}}" # {{server}} = cluster API endpoint
19 namespace: monitoring
20 syncPolicy:
21 automated:
22 prune: true
23 selfHeal: trueRegister a new cluster in Argo CD, apply the environment=production label, and within seconds the monitoring ApplicationSet deploys the monitoring stack to it. No manual Application creation.
Cluster annotations are accessible as {{metadata.annotations.KEY}} and labels as {{metadata.labels.KEY}}, enabling cluster-specific values without a separate values file per cluster.
Git Generator: Directory Mode
Generates one Application per subdirectory in a Git repository. This is the canonical pattern for GitOps application onboarding — add a directory, get an Application.
1spec:
2 generators:
3 - git:
4 repoURL: https://github.com/my-org/fleet
5 revision: HEAD
6 directories:
7 - path: apps/* # Match all direct subdirs of apps/
8 - path: apps/excluded # Exclude a specific directory
9 exclude: true
10 template:
11 metadata:
12 name: "{{path.basename}}" # {{path.basename}} = directory name
13 spec:
14 source:
15 repoURL: https://github.com/my-org/fleet
16 targetRevision: HEAD
17 path: "{{path}}" # {{path}} = relative path from root
18 destination:
19 server: https://kubernetes.default.svc
20 namespace: "{{path.basename}}"Add a directory apps/new-service/ containing Kubernetes manifests or a Kustomize overlay, and Argo CD automatically creates and syncs the new-service Application. Delete the directory, and Argo CD deletes the Application (and optionally prunes the resources).
Git Generator: Files Mode
Generates Applications from JSON/YAML configuration files in a repo:
1spec:
2 generators:
3 - git:
4 repoURL: https://github.com/my-org/fleet
5 revision: HEAD
6 files:
7 - path: "clusters/**/config.json" # Match all config.json files recursively
8 template:
9 metadata:
10 name: "{{cluster.name}}-{{app.name}}"
11 spec:
12 source:
13 path: "{{app.path}}"
14 helm:
15 values: |
16 image:
17 tag: "{{app.version}}"
18 destination:
19 server: "{{cluster.address}}"
20 namespace: "{{app.namespace}}"Each config.json can contain structured data:
1{
2 "cluster": {
3 "name": "us-east-1-prod",
4 "address": "https://k8s-prod-us.example.com"
5 },
6 "app": {
7 "name": "payments-api",
8 "path": "apps/payments-api",
9 "namespace": "production",
10 "version": "v2.1.4"
11 }
12}This pattern works well for teams that want explicit, reviewed configuration files for each deployment rather than implicit directory-scanning.
Matrix Generator
Generates the Cartesian product of two generators. Deploy every application to every cluster without listing all combinations:
1spec:
2 generators:
3 - matrix:
4 generators:
5 # Inner generator 1: all production clusters
6 - clusters:
7 selector:
8 matchLabels:
9 environment: production
10 # Inner generator 2: all applications from Git
11 - git:
12 repoURL: https://github.com/my-org/fleet
13 revision: HEAD
14 directories:
15 - path: platform-apps/*
16 template:
17 metadata:
18 name: "{{path.basename}}-{{name}}" # app-cluster
19 spec:
20 source:
21 path: "{{path}}"
22 destination:
23 server: "{{server}}"
24 namespace: "{{path.basename}}"3 clusters × 5 applications = 15 Applications, all from one ApplicationSet. Adding a new cluster or a new app directory automatically adds all the missing combinations.
SCM Provider Generator
Discovers repositories from GitHub, GitLab, or Bitbucket organisations and creates Applications for each:
1spec:
2 generators:
3 - scmProvider:
4 github:
5 organization: my-org
6 appSecretName: github-token # PAT with repo read access
7 allBranches: false # Only default branch
8 filters:
9 - repositoryMatch: ".*-service$" # Only repos ending in -service
10 - labelMatch: "deploy-to-argocd" # Only repos with this topic
11 template:
12 metadata:
13 name: "{{repository}}"
14 spec:
15 source:
16 repoURL: "{{url}}"
17 targetRevision: "{{branch}}"
18 path: deploy/ # Assume each repo has a deploy/ directory
19 destination:
20 server: https://kubernetes.default.svc
21 namespace: "{{repository}}"New repositories matching the filter are automatically added; deleted repositories trigger Application deletion. This pattern works well for microservices organisations where teams own their own repositories.
Sync Waves and Hooks
Sync waves control the order resources are applied during a sync. Lower wave numbers are applied first; Argo CD waits for all resources in a wave to become healthy before proceeding to the next:
1# Applied first (wave -1): CRDs and namespaces
2apiVersion: v1
3kind: Namespace
4metadata:
5 name: payments
6 annotations:
7 argocd.argoproj.io/sync-wave: "-1"
8---
9# Applied second (wave 0, default): main application resources
10apiVersion: apps/v1
11kind: Deployment
12metadata:
13 name: payments-api
14 annotations:
15 argocd.argoproj.io/sync-wave: "0"
16---
17# Applied last (wave 1): resources that depend on the main deployment
18apiVersion: batch/v1
19kind: Job
20metadata:
21 name: post-deploy-migration
22 annotations:
23 argocd.argoproj.io/sync-wave: "1"Note: Sync-wave annotations on Application objects only control resource ordering within a single Application's sync — they do not cause the ApplicationSet controller to deploy Applications sequentially across clusters. For true progressive multi-cluster rollouts (deploy to staging, wait, then deploy to prod), use ApplicationSet
spec.strategy.rollingSyncwith theARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_PROGRESSIVE_SYNCS=truefeature gate (beta in ArgoCD v2.10+).
Sync hooks run at specific sync lifecycle phases:
1apiVersion: batch/v1
2kind: Job
3metadata:
4 name: db-migration
5 annotations:
6 argocd.argoproj.io/hook: PreSync # Run before the sync
7 argocd.argoproj.io/hook-delete-policy: HookSucceeded # Delete after success
8spec:
9 template:
10 spec:
11 containers:
12 - name: migrate
13 image: my-app:latest
14 command: ["./migrate", "--apply"]
15 restartPolicy: NeverHook phases: PreSync (before sync), Sync (during sync), PostSync (after all resources are healthy), SyncFail (on sync failure — for cleanup).
Progressive Multi-Cluster Delivery
Deploy to staging first, wait for health checks, then deploy to production — across an entire cluster fleet:
1spec:
2 generators:
3 - list:
4 elements:
5 - cluster: staging
6 wave: "0"
7 - cluster: canary-prod
8 wave: "1" # Canary production cluster — deploy after staging
9 - cluster: production-us-east-1
10 wave: "2" # Full production — deploy after canary is healthy
11 - cluster: production-eu-west-1
12 wave: "2"
13 template:
14 metadata:
15 name: "my-app-{{cluster}}"
16 annotations:
17 argocd.argoproj.io/sync-wave: "{{wave}}"
18 spec:
19 ...Combined with Argo CD's sync waves and health checks, this creates a multi-cluster progressive delivery pipeline entirely within GitOps.
Frequently Asked Questions
How does ApplicationSet handle cluster failures?
If a target cluster is unreachable, its Application shows as Unknown or Degraded — ApplicationSet continues managing other cluster Applications normally. The ApplicationSet itself doesn't fail; only the affected Application shows the error. With syncPolicy.automated, Argo CD retries syncs with exponential backoff.
Can I use ApplicationSet with Helm values per cluster?
Yes. Using the Cluster generator, Argo CD cluster labels and annotations are accessible as template variables. For cluster-specific Helm values, combine with the Git Files generator — each cluster has a values.yaml file that the Application references:
helm:
valueFiles:
- "clusters/{{name}}/values.yaml" # Per-cluster values file in repo
- values.yaml # Shared default valuesWhat's the difference between ApplicationSet and app-of-apps?
App-of-apps uses a root Application that points to a directory of Application YAML files — you write and maintain each Application manifest. ApplicationSet generates Application manifests dynamically from a template and generator — you don't write or maintain individual Application manifests. For large fleets, ApplicationSet is significantly less operational overhead. For small numbers of applications with very different configurations, app-of-apps is simpler. They can also be combined: an ApplicationSet deploys a root app-of-apps Application per cluster, which in turn manages cluster-specific applications.
For Argo CD production setup including app-of-apps, RBAC, and cluster registration, see GitOps with Argo CD: Production Setup Guide. For the Flux CD alternative to ApplicationSet for multi-cluster GitOps, see Flux CD: GitOps for Kubernetes. For the progressive delivery layer on top of ApplicationSet, see Argo Rollouts: Canary and Blue-Green Deployments.
Building a multi-cluster GitOps platform with Argo CD? Talk to us at Coding Protocols — we help platform teams design ApplicationSet patterns that scale from 5 clusters to 500.


