Argo CD ApplicationSets: Generating Dozens of Applications from One Template
ApplicationSets generate Argo CD Applications from one template plus a generator. Learn how the mechanism works, see a cluster-generator example, and avoid orphaned apps and broad selectors.
06 May 2026, 15:57 UTC

If you manage more than a handful of clusters or tenants with Argo CD, hand-writing an Application manifest for every cluster/environment combination stops scaling quickly. ApplicationSets solve this: you write one template plus a generator that produces the parameter values, and the ApplicationSet controller creates and maintains the individual Application resources for you. Add a cluster, update a label, or commit a new directory to Git, and the matching Applications appear (or disappear) automatically.
This guide explains how the generator-plus-template mechanism works, walks through a concrete multi-cluster example, and covers the failure modes that bite people in production: orphaned Applications, runaway Git polling, and overly broad cluster selectors. Examples assume Argo CD 2.x with the ApplicationSet controller installed (it ships bundled with Argo CD since v2.3, but verify against your version's documentation).
The mechanism: generator output feeds a template
An ApplicationSet has two parts:
- Generators produce a list of parameter sets — plain key/value maps like
{cluster: prod-eu, url: https://...}. - The template is a normal Argo CD
Applicationspec with{{parameter}}placeholders. The controller renders the template once per parameter set and creates one Application per result.
The controller reconciles continuously: when generator output changes (a new cluster secret appears, a Git directory is added), it creates, updates, or — depending on configuration — deletes the corresponding Applications. The ApplicationSet itself lives in Argo CD's control-plane namespace (usually argocd), and by default the generated Applications are created there too.
Choosing a generator
The four most commonly used generators map to different sources of truth:
| Generator | Source of truth | Best for |
|---|---|---|
| List | Elements hard-coded in the ApplicationSet manifest | Small, stable sets (e.g., three fixed environments) |
| Cluster | Labels on Argo CD cluster secrets | Deploying an agent/add-on to every matching cluster |
| Git (directories) | Directory layout in a Git repo | Self-service: each team/tenant gets a folder |
| Git (files) | Contents of config files in a Git repo | Rich per-target parameters stored in Git |
Generators can also be combined with merge or matrix generators (e.g., every cluster × every tenant), but start simple — combinatorial generators multiply the number of Applications quickly.
Worked example: cluster generator with a safety selector
The following ApplicationSet deploys a monitoring agent to every registered cluster labeled env: production. Run kubectl apply against the cluster hosting Argo CD, in the argocd namespace, with permissions to create ApplicationSets (cluster-admin or an equivalent Argo CD RBAC grant).
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: monitoring-agent
namespace: argocd
spec:
generators:
- clusters:
selector:
matchLabels:
env: production
template:
metadata:
name: 'monitoring-{{name}}'
spec:
project: platform
source:
repoURL: https://github.com/example/platform-addons.git
targetRevision: main
path: charts/monitoring-agent
destination:
server: '{{server}}'
namespace: monitoring
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=trueHere the cluster generator emits parameters such as {{name}} (the cluster secret name) and {{server}} (its API URL) for each cluster secret carrying env: production. Label a new cluster secret and a monitoring-<name> Application appears within the controller's reconciliation interval.
To verify, run on the Argo CD host cluster:
kubectl get applicationsets -n argocd
kubectl get applications -n argocd | grep monitoringYou should see one Application per matching cluster, each named from the template. Then label an additional cluster secret (kubectl label secret <cluster-secret> -n argocd env=production) and confirm a new Application is generated. Do not assume instant creation — check the ApplicationSet controller logs (kubectl logs -n argocd deploy/argocd-applicationset-controller) if nothing appears within a few minutes.
Deletion behavior: the orphaned-Application trap
Deleting an ApplicationSet does not reliably cascade to its generated Applications in all versions and configurations. The result can be Applications that keep syncing workloads with no parent managing them. Before deleting an ApplicationSet, explicitly delete the generated Applications first (they are identifiable by name pattern or owner references), then remove the ApplicationSet. Verify afterward with kubectl get applications -n argocd that nothing remains. Treat this as a manual checklist, not an assumption — behavior here has changed across releases, so confirm against your installed version.
Common mistakes and limits
- Overly broad cluster selectors. A Cluster generator with no selector targets every registered cluster, including production. Always scope with
matchLabelsand test against a staging label first. - Git generator on large repos. The Git generator polls the repository; on repos with many directories or frequent commits this adds API load and slows reconciliation. Point it at a dedicated config repo or subdirectory, and be aware the default requeue interval means changes are not instantaneous.
- Name collisions. If two parameter sets render the same Application name, they fight over one resource. Include a unique parameter (cluster name, tenant ID) in
template.metadata.name. - Template changes are global. Editing the template updates every generated Application at once. Stage template changes in a branch and review the rendered diff before merging.
- Go template syntax drift. Newer versions support
goTemplate: truewith different parameter syntax ({{.name}}vs{{name}}). Mixing them silently breaks rendering — check which mode your version defaults to.
A practical rollout path
- Start with a List generator and two or three explicit targets — it is the easiest to reason about and debug.
- Move to a Cluster or Git generator once the template is stable, with a restrictive selector or a small config repo.
- Enable automated sync only after you have watched the generated Applications render correctly with manual sync.
- Before any ApplicationSet deletion, inventory and remove generated Applications first, then verify with
kubectl get applications.
The payoff is real: cluster onboarding becomes "apply a label" instead of "write and review a new manifest." The cost is that mistakes in the template or selector are also multiplied — so keep selectors tight and treat the template as production code.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.