App-of-Apps & ApplicationSets
App-of-Apps & ApplicationSets
A single ArgoCD Application resource works well for one service. A real platform team manages hundreds of services across dozens of clusters. Manually creating and maintaining one Application YAML per microservice — and then doing that again for staging, production, and each region — does not scale. ArgoCD solves this with two complementary patterns: App-of-Apps and ApplicationSets.
The App-of-Apps Pattern
The idea is simple: a root ArgoCD Application points at a Git directory that contains other ArgoCD Application manifests. When ArgoCD syncs the root, it discovers and creates the child applications. Those children are themselves full Application objects — they each point at their own Git source and each have their own sync policy.
A minimal root app manifest looks like this:
resources-finalizer.argocd.argoproj.io is critical. Without it, deleting the Application object in ArgoCD leaves all the Kubernetes resources (Deployments, Services, PVCs) orphaned in the cluster. Always include it on every child application.
The root application itself is seeded once — typically via kubectl apply or ArgoCD's own install chart. After that, adding a new service means committing a new YAML file to the apps/root/ directory; ArgoCD picks it up automatically on the next sync.
ApplicationSets: Generator-Driven Scaling
App-of-Apps works well but still requires you to write one YAML per service per environment. ApplicationSets (now stable, part of ArgoCD core since v2.3) go further: a single ApplicationSet resource uses generators to produce many Application objects from a template. No copy-paste, no manual files per cluster.
The most important generators are:
- List — inline list of key-value maps; the simplest generator for small, static sets.
- Git — auto-discovers apps by scanning Git directories or files; great for monorepos.
- Matrix — Cartesian product of two generators; e.g., services × environments.
- Cluster — iterates over clusters registered in ArgoCD; deploy to every cluster automatically.
- SCM Provider — scans all repos in a GitHub org; good for platform-wide enforcement.
- Pull Request — creates a temporary Application for each open PR; enables preview environments.
Matrix Generator: Services × Environments
This is the production workhorse. The outer generator yields environments, the inner yields services; ArgoCD renders one Application per pair:
goTemplate: true plus goTemplateOptions: ["missingkey=error"] on every ApplicationSet. The older {{env}} fasttemplate syntax silently renders undefined variables as empty strings, hiding configuration bugs. Go templates fail loudly on missing keys, which is exactly what you want in production.
Git Generator: Auto-Discovery in a Monorepo
When your monorepo uses a convention like services/<name>/kustomization.yaml, the Git file generator discovers new services automatically — no ApplicationSet change required when a developer adds a new directory:
ApplicationSet Sync Policy and Deletion Protection
ApplicationSets introduce an important risk: a generator misconfiguration (a typo in a directory glob, for example) can produce zero results, causing ArgoCD to delete all child applications and prune every resource in the cluster. Protect against this with the preserveResourcesOnDeletion policy:
prune: true on the generated child apps without also setting preserveResourcesOnDeletion: true on the ApplicationSet itself. A bad generator update wiped a full production environment at a major fintech in 2023 because the directory glob returned zero matches — ArgoCD pruned all apps, which pruned all workloads.
Project Isolation
At scale you will have multiple teams. ArgoCD Projects enforce RBAC boundaries: which source repos a team may use, which clusters and namespaces they may deploy to, and which resource kinds they are allowed to create. ApplicationSets should always target a team-specific project rather than default:
Operational Best Practices
- One ApplicationSet per team, not per service. Fewer objects to reason about; generators handle the fan-out.
- Use
ServerSideApply=trueinsyncOptionswhen multiple controllers manage the same object (e.g., HPA + ArgoCD). It eliminates field-manager conflicts. - Test generators locally with
argocd appset generate ./my-appset.yamlbefore applying — see what Applications would be created without touching the cluster. - Cap concurrent reconciliation with
--application-set-concurrent-reconciliations=20on the applicationset-controller; an uncapped ApplicationSet managing 500 apps can spike API server load on sync. - Tag child apps with
labels(env, team, region) so you can filter in the ArgoCD UI and write targeted RBAC policies.
Application manifests committed to Git — easy to review in PRs. ApplicationSets give you generator-driven automation with zero per-app YAML. Big-tech platforms typically use both: ApplicationSets for homogeneous workloads (all microservices), and App-of-Apps for heterogeneous infrastructure bootstrapping (cert-manager, ingress, monitoring stack) where each app needs unique configuration that does not fit a template.