Context & Why: The Multi-Cluster Management Problem
Picture this: you’re running a platform team responsible for 12 microservices deployed across 3 Kubernetes clusters — staging, production-us, and production-eu. Every time you add a new service or onboard a new cluster, someone manually copies Argo CD Application manifests, tweaks a few values, and hopes nothing breaks. It works when you have 10 applications. At 50, it becomes fragile. At 200, it’s unmanageable.
I’ve been there. Copy-paste sprawl catches up fast — and the root cause isn’t Argo CD’s limitations. A single Application resource maps to exactly one app on exactly one cluster. No loop, no templating, no automatic discovery.
That’s the gap Argo CD ApplicationSet fills. Instead of writing one Application per service per cluster, you write one ApplicationSet that generates all of them automatically — using generators that pull from your cluster list, Git repo structure, or a simple matrix of combinations. I learned this after spending two days manually syncing 40 Application manifests when we added a fourth cluster. One ApplicationSet would have handled it in minutes.
ApplicationSet ships as part of Argo CD since v2.3 and runs as a separate controller that watches ApplicationSet resources and creates, updates, or deletes the resulting Application objects automatically.
Installation
Prerequisites
- Kubernetes cluster (v1.22+) for the Argo CD control plane
kubectlconfigured with cluster-admin accessargocdCLI installed- Target clusters registered in Argo CD (for multi-cluster scenarios)
Install Argo CD with ApplicationSet Controller
Create the namespace and install Argo CD. The ApplicationSet controller is bundled in the standard install since v2.3, so no separate installation step is needed:
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
Wait for all components to be ready:
kubectl wait --for=condition=available deployment --all -n argocd --timeout=120s
Verify the ApplicationSet controller is running:
kubectl get deployment argocd-applicationset-controller -n argocd
# NAME READY UP-TO-DATE AVAILABLE
# argocd-applicationset-controller 1/1 1 1
Access the Argo CD UI and CLI
# Get the initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d
# Port-forward to access UI locally
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Log in via CLI
argocd login localhost:8080 --username admin --password <PASSWORD> --insecure
Register Target Clusters
For multi-cluster deployments, register each target cluster. The ApplicationSet will reference these clusters by name or URL:
# Add a remote cluster (run from a kubeconfig context pointing to that cluster)
argocd cluster add production-us --name production-us
argocd cluster add production-eu --name production-eu
argocd cluster add staging --name staging
# Verify registered clusters
argocd cluster list
Configuration: Writing Your First ApplicationSet
Understanding Generators
An ApplicationSet has two key sections: a generator that produces a list of parameter sets, and a template that uses those parameters to render Application manifests. The generator defines what gets created — the controller handles the rest.
The most common generators are:
- List generator — explicit list of values you define
- Cluster generator — automatically uses registered Argo CD clusters
- Git generator — discovers apps from your Git repo directory structure
- Matrix generator — combines two generators (e.g., every app × every cluster)
List Generator: Deploy One App to Multiple Clusters
Start with the simplest case — deploying a single application to multiple environments:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: nginx-ingress-appset
namespace: argocd
spec:
generators:
- list:
elements:
- cluster: staging
url: https://staging.k8s.internal
namespace: ingress-nginx
values:
replicaCount: "1"
- cluster: production-us
url: https://prod-us.k8s.internal
namespace: ingress-nginx
values:
replicaCount: "3"
- cluster: production-eu
url: https://prod-eu.k8s.internal
namespace: ingress-nginx
values:
replicaCount: "3"
template:
metadata:
name: "nginx-ingress-{{cluster}}"
spec:
project: default
source:
repoURL: https://charts.helm.sh/stable
chart: nginx-ingress
targetRevision: "1.41.3"
helm:
parameters:
- name: controller.replicaCount
value: "{{values.replicaCount}}"
destination:
server: "{{url}}"
namespace: "{{namespace}}"
syncPolicy:
automated:
prune: true
selfHeal: true
Apply this manifest:
kubectl apply -f nginx-ingress-appset.yaml -n argocd
Argo CD creates three Application resources right away — one per cluster.
Git Generator: Auto-Discover Apps from Repository Structure
The Git generator is the real force multiplier here. Organize your apps as directories in a Git repo and let the generator discover them automatically. Adding a new directory automatically creates a new Application — no manifest editing required.
Assume your repo has this structure:
apps/
├── api-gateway/
│ └── kustomization.yaml
├── auth-service/
│ └── kustomization.yaml
├── billing-service/
│ └── kustomization.yaml
└── user-service/
└── kustomization.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: microservices-appset
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/your-org/k8s-manifests.git
revision: HEAD
directories:
- path: apps/*
template:
metadata:
name: "{{path.basename}}"
spec:
project: default
source:
repoURL: https://github.com/your-org/k8s-manifests.git
targetRevision: HEAD
path: "{{path}}"
destination:
server: https://kubernetes.default.svc
namespace: "{{path.basename}}"
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
With four directories in apps/, this single ApplicationSet generates four Application resources. Push a fifth directory — a fifth Application appears automatically, usually within 30 seconds.
Matrix Generator: Every App on Every Cluster
Combine the Git generator with the Cluster generator to deploy every discovered app to every registered cluster — the full cross-product:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: all-apps-all-clusters
namespace: argocd
spec:
generators:
- matrix:
generators:
- git:
repoURL: https://github.com/your-org/k8s-manifests.git
revision: HEAD
directories:
- path: apps/*
- clusters:
selector:
matchLabels:
environment: production
template:
metadata:
name: "{{path.basename}}-{{name}}"
spec:
project: default
source:
repoURL: https://github.com/your-org/k8s-manifests.git
targetRevision: HEAD
path: "{{path}}"
destination:
server: "{{server}}"
namespace: "{{path.basename}}"
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
The clusters.selector field filters clusters by labels you’ve set on them. Label your clusters when registering:
# List registered cluster secrets to find the secret name
kubectl get secret -n argocd -l argocd.argoproj.io/secret-type=cluster
# Label the appropriate secret (replace <secret-name> with the name from above)
kubectl label secret <secret-name> -n argocd environment=production
Verification & Monitoring
Check Generated Applications
With the ApplicationSet applied, check that Application resources appeared as expected:
# List all generated Applications
argocd app list
# Filter by ApplicationSet label (automatically added)
kubectl get applications -n argocd \
-l argocd.argoproj.io/appset-name=microservices-appset
Monitor Sync Status
# Check sync status across all apps at once
argocd app list -o wide
# Watch for sync issues
argocd app list | grep -v Synced
# Get detailed status of a specific generated app
argocd app get api-gateway
argocd app logs api-gateway
Force Sync All Apps in an ApplicationSet
# Sync all apps matching a label selector
kubectl get applications -n argocd \
-l argocd.argoproj.io/appset-name=microservices-appset \
-o name | xargs -I{} argocd app sync {}
Debugging ApplicationSet Issues
If Applications aren’t being generated, start with the controller logs:
kubectl logs -n argocd deployment/argocd-applicationset-controller --tail=50 -f
Three problems come up most often:
- No apps generated from Git generator — verify the repo is accessible and the path glob matches actual directories:
argocd repo list - Cluster not matched by selector — check cluster labels:
kubectl get secret -n argocd -l argocd.argoproj.io/secret-type=cluster -o yaml - Template rendering errors — check the ApplicationSet status field:
kubectl describe applicationset <name> -n argocd
Setting Up Notifications
Argo CD’s notification controller integrates with Slack, PagerDuty, and email. Add a trigger for sync failures so you’re notified when any generated app drifts:
# In argocd-notifications-cm ConfigMap
triggers: |
trigger.on-sync-failed: |
- when: app.status.operationState.phase in ['Error', 'Failed']
send: [app-sync-failed]
In practice, Matrix generator + Slack notifications together change how you think about cluster sprawl. When a new team pushes a directory to the apps/ path and their service appears in Argo CD within minutes — already synced across all production clusters — that’s a concrete sign the setup is paying off.
ApplicationSet doesn’t replace good deployment practices, but it removes the repetitive scaffolding that slows teams down. Start with the List generator to understand the template system, move to Git-based discovery for your own services, and only reach for Matrix when you genuinely need full cross-product deployment. Complexity is a cost — pay it only when the scale demands it.

