One Application per service per environment means dozens of ArgoCD Application YAMLs. The App-of-Apps pattern and ApplicationSet generator solve this — one definition, infinite applications. This is how platform teams scale GitOps.
The Scaling Problem
Pattern 1: App-of-Apps
apps/ folder. Each YAML in that folder becomes a managed ArgoCD Application.Root Application
# apps/root.yaml (apply this manually ONCE)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/YOUR_USER/cicd-mastery-gitops.git
targetRevision: main
path: apps
directory:
recurse: false
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated: { prune: true, selfHeal: true }
Child Application (just add files to apps/)
# apps/api-staging.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api-staging
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/YOUR_USER/cicd-mastery-gitops.git
path: environments/staging
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: staging
syncPolicy:
automated: { prune: true, selfHeal: true }
syncOptions: [CreateNamespace=true]
Adding a new service: Drop a new YAML in apps/ → push → root app syncs → child app created → service deployed. Zero manual ArgoCD interaction.
Pattern 2: ApplicationSet (Even Better)
Instead of writing one YAML per app, generate them from a template:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: all-services
namespace: argocd
spec:
generators:
# Auto-discover directories matching pattern
- git:
repoURL: https://github.com/YOUR_USER/cicd-mastery-gitops.git
revision: main
directories:
- path: 'services/*/staging'
- path: 'services/*/production'
template:
metadata:
# {{path[1]}} = service name, {{path[2]}} = environment
name: '{{path[1]}}-{{path[2]}}'
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/YOUR_USER/cicd-mastery-gitops.git
targetRevision: main
path: '{{path}}'
destination:
server: https://kubernetes.default.svc
namespace: '{{path[2]}}'
syncPolicy:
automated: { prune: true, selfHeal: true }
syncOptions: [CreateNamespace=true]
ApplicationSet is the platform engineering tool. Create a new directory (
services/payment/staging/) with manifests → push → ApplicationSet generator auto-creates the ArgoCD Application → service deployed. Zero config, zero tickets.
When to Use Which
| Pattern | Best For | Tradeoff |
|---|---|---|
| Individual Apps | 1-3 services, simple setup | Doesn't scale past ~10 apps |
| App-of-Apps | 5-20 services, per-app customization | Still one YAML per app, but managed automatically |
| ApplicationSet | 20+ services, convention-driven | Less per-app control, more consistency |
🧠 Recall Check
- What does the "root" application in App-of-Apps watch?
- How do you add a new service with App-of-Apps?
- What does an ApplicationSet generator do?
- As an architect with 30 microservices, which pattern would you choose?
Reveal answers
- The
apps/directory in the GitOps repo. Every YAML file in there becomes a managed ArgoCD Application. - Add a new Application YAML file to
apps/and push. The root app auto-syncs and creates it. - It auto-discovers (e.g., directories matching a pattern) and generates Application resources from a template. No per-service YAML needed.
- ApplicationSet. At 30 services, you want convention-over-configuration. Define the directory pattern once, and every new service that follows the convention is automatically deployed.
Next lesson: Argo Rollouts — progressive delivery (canary + blue-green) with automated analysis.