ArgoCD ApplicationSet Generators Deep Dive
List, Cluster, Git, Matrix, and Merge generators - templating many ArgoCD Applications from a single manifest
Introduction
A single Application manifest points ArgoCD at one Git path and deploys it to one cluster. That breaks down fast once you have the same app going to 10 clusters, or a monorepo with 30 services each needing their own Application - hand-maintaining 30 near-identical manifests is exactly the kind of copy-paste drift GitOps is supposed to eliminate. ApplicationSet solves this: one manifest with a generator that produces a list of parameters, and a template that gets rendered once per generated entry into a real Application.
The Basic Shape
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: example
namespace: argocd
spec:
generators:
- list:
elements:
- cluster: staging
url: https://staging.example.com
- cluster: production
url: https://prod.example.com
template:
metadata:
name: '{{cluster}}-web-app'
spec:
project: default
source:
repoURL: https://github.com/example/web-app.git
targetRevision: main
path: manifests
destination:
server: '{{url}}'
namespace: web-app
syncPolicy:
automated:
prune: true
selfHeal: true
Every generator produces the same shape internally: a list of key/value parameter sets. The template block is rendered once per set, with {{key}} substituted from each - here, the List generator’s two hardcoded entries produce two Application objects, staging-web-app and production-web-app, each pointed at a different cluster.
Cluster Generator
spec:
generators:
- clusters:
selector:
matchLabels:
env: production
template:
metadata:
name: '{{name}}-web-app'
spec:
source:
repoURL: https://github.com/example/web-app.git
targetRevision: main
path: manifests
destination:
server: '{{server}}'
namespace: web-app
The Cluster generator produces one entry per cluster registered with ArgoCD (argocd cluster add), filtered by selector against the labels on each cluster’s Secret. Add a new cluster labeled env: production and it’s picked up automatically on the next reconcile - no manifest edit required, which is the main advantage over a List generator for a fleet that actually grows over time.
Git Generator
spec:
generators:
- git:
repoURL: https://github.com/example/services.git
revision: main
directories:
- path: services/*
template:
metadata:
name: '{{path.basename}}'
spec:
source:
repoURL: https://github.com/example/services.git
targetRevision: main
path: '{{path}}'
destination:
server: https://kubernetes.default.svc
namespace: '{{path.basename}}'
The Git directory generator scans the repo for directories matching services/* and produces one entry per match - {{path}} is the full matched path, {{path.basename}} just the final segment. This is the monorepo pattern: add a new folder under services/, and ArgoCD creates a new Application for it on the next Git poll, with no ApplicationSet edit at all. A files variant (files: - path: services/*/config.json) does the same but reads structured parameters out of a JSON/YAML file in each matched location instead of just the directory name.
Matrix Generator
spec:
generators:
- matrix:
generators:
- clusters:
selector:
matchLabels:
env: production
- git:
repoURL: https://github.com/example/services.git
revision: main
directories:
- path: services/*
template:
metadata:
name: '{{name}}-{{path.basename}}'
spec:
source:
repoURL: https://github.com/example/services.git
targetRevision: main
path: '{{path}}'
destination:
server: '{{server}}'
namespace: '{{path.basename}}'
Matrix takes the Cartesian product of two (or more) child generators - every cluster crossed with every service directory. Three production clusters and ten services in the Git generator produces thirty Application objects, one per combination - the pattern for “deploy every one of these services to every one of these clusters” without writing either list by hand.
Merge Generator
spec:
generators:
- merge:
mergeKeys:
- cluster
generators:
- clusters: {}
- list:
elements:
- cluster: production
replicas: "5"
Merge combines generators by a shared key instead of a full cross-product - here, every cluster from the Cluster generator gets merged with the List generator’s overrides where cluster matches, letting production pick up replicas: "5" while every other cluster keeps whatever the base generator produced. Useful for “mostly uniform, with a few explicit per-target overrides” instead of the Matrix generator’s full expansion.
Go Templating
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- list:
elements:
- cluster: production
replicas: 5
template:
metadata:
name: '{{.cluster}}-web-app'
spec:
source:
helm:
parameters:
- name: replicaCount
value: '{{.replicas}}'
goTemplate: true switches the template’s substitution engine from the default fasttemplate ({{key}}, string-only, no logic) to real Go templates ({{.key}}, with if/range/functions available) - worth turning on once a template needs conditionals or loops over a parameter that’s itself a list, which fasttemplate can’t express at all.
Best Practices
- Prefer the Cluster or Git generator over List once the set of targets is expected to grow - List requires a manifest edit for every new entry; Cluster and Git generators pick up new clusters/directories automatically.
- Set
syncPolicy.automateddeliberately per ApplicationSet, not by copying it from an example - an ApplicationSet with a bad template and auto-sync enabled can misconfigure every generated Application simultaneously. - Use
goTemplate: truefrom the start on any new ApplicationSet unless you have a specific reason not to - migrating an existing one later means rewriting every{{key}}to{{.key}}. - Test generator output with
argocd appset generatebefore relying on the controller’s own reconcile cycle to surface a templating mistake - it prints the resolved parameter sets without creating anything.
Conclusion
Generators are what let ArgoCD scale from “one Application per team member managing it by hand” to “a fleet of Applications derived declaratively from cluster registrations and repo structure” - the right generator choice (List for a small fixed set, Cluster/Git for anything that grows, Matrix/Merge for combining dimensions) is what keeps that fleet self-maintaining instead of becoming its own manual-toil problem.