29.7 ApplicationSets: Templating Across Clusters and Environments
Right, so you’ve got one application. You’ve got one cluster. You’ve got one ArgoCD Application resource pointing at one Helm chart. It’s a beautiful, simple, monogamous relationship. Then your boss walks in and says, “Great, now do that for 12 microservices across 3 environments and 5 regional clusters.” Suddenly, your neat little YAML file looks less like a solution and more like a threat.
This is where you stop copy-pasting Application manifests and start using ApplicationSets. Think of an ApplicationSet as a factory—or, if you’re feeling cheeky, a YAML-cloning machine. It’s a custom resource that takes a template of an ArgoCD Application and generates actual Application resources based on a list of parameters you provide. It’s the only sane way to manage applications at scale without drowning in a sea of almost-identical YAML.
The Generators: Where the Magic (and Confusion) Happens
The heart of an ApplicationSet is its generator. This is the engine that says, “Here are all the places I need to deploy this thing.” The main ones you’ll use are list, cluster, and git.
The list generator is the simplest. You literally just provide a list of name/value pairs. It’s perfect for when you have a known, finite set of parameters, like a few environments.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-app-dev-staging
spec:
generators:
- list:
elements:
- cluster: us-east-1
env: dev
- cluster: us-west-2
env: staging
template:
metadata:
name: '{{name}}-{{generator.element.cluster}}'
spec:
project: default
source:
repoURL: 'https://github.com/myorg/my-app.git'
targetRevision: HEAD
path: 'helm/{{generator.element.env}}'
destination:
server: 'https://kubernetes.default.svc'
namespace: 'my-app-{{generator.element.env}}'
syncPolicy:
automated: {}
See what happened there? The generator spits out two sets of parameters. The template takes those parameters and uses Go templating ({{ }}) to fill in the blanks. One Application will be created for dev on us-east-1, and another for staging on us-west-2. The name field in the template is crucial; it has to be unique for each generated Application, so you must include a parameter in it. I’ve seen people forget this and then spend an hour wondering why ArgoCD is yelling at them.
The Cluster Generator: For When You’re Actually Doing This Right
The list generator is fine, but hardcoding your clusters feels… wrong. Enter the cluster generator. This beauty automatically discovers the clusters you’ve already defined in ArgoCD and uses them as its parameter list. This is GitOps nirvana: add a new cluster to ArgoCD, and your ApplicationSet automatically deploys to it.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-app-all-clusters
spec:
generators:
- clusters: {} # Just... all of them. Buckle up.
template:
metadata:
name: '{{name}}-{{cluster.name}}'
spec:
project: default
source:
repoURL: 'https://git.example.com/my-app-helm.git'
targetRevision: main
path: helm/production # Warning: This deploys prod everywhere!
destination:
server: '{{cluster.server}}'
namespace: my-app
A huge “gotcha” here: the cluster generator is a blunt instrument. It will deploy your template to every cluster known to ArgoCD, including the host cluster where ArgoCD itself lives (https://kubernetes.default.svc). This is often not what you want. You need to use matchLabels or selector in the generator to filter clusters, or get clever with template logic to avoid, say, deploying a monitoring stack onto your monitoring stack’s cluster. It’s a common foot-gun.
Taming the Beast: Selective Deployment and Best Practices
You’re not a maniac, so you don’t want to deploy everything to everywhere. You need control. This is where labels and the cluster generator’s selector come in.
First, when you add a cluster to ArgoCD, label it meaningfully:
argocd cluster add my-cluster-01 --label region=emea --label env=staging
Then, your ApplicationSet can be surgical:
generators:
- clusters:
selector:
matchLabels:
env: staging
region: emea
template:
spec:
source:
path: 'helm/{{cluster.labels.env}}' # Now the env label picks the right chart!
Another best practice: keep your generators and templates separate. Your ApplicationSet YAML should be a generator of Applications, not a complex Helm chart itself. The real business logic—different values per environment—should live in the Helm chart’s values-prod.yaml and values-staging.yaml files, or in the Git repo paths you’re targeting. The ApplicationSet just provides the parameters to choose the right one.
Finally, for the love of all that is holy, use sync windows and project restrictions. An ApplicationSet with automated syncs is a powerful automaton. A powerful automaton with a bug in its template is a great way to accidentally deploy a latest tag to production at 3 AM. Use ArgoCD’s built-in safeguards because this thing will move fast and break things if you let it. It’s brilliant, but it demands respect.