App-of-Apps Bootstrap Pattern#
What We Built#
A root ArgoCD Application that watches environments/dev/apps/ and manages
every Application manifest it finds there. A single kubectl apply bootstraps
all services in the environment; after that, adding or removing a service is a
git operation only.
How It Works#
| |
ArgoCD manages Application objects the same way it manages any other Kubernetes
resource. The child Applications are reconciled from git - if you delete
environments/dev/apps/svc2.yaml, ArgoCD deletes svc2-dev (and its workloads,
because prune: true).
Structure#
| |
The root Application used to live at
bootstrap/root-app-dev.yaml(gitignored). It was moved toenvironments/dev/bootstrap.yamlso all environment bootstrap files live alongside their environment - consistent with theeu-dev-rancher/bootstrap.yamlpattern.
The Root Application#
environments/dev/bootstrap.yaml:
| |
destination.namespace: argocd - all ArgoCD Application CRDs live in the
argocd namespace regardless of how they are created (manually, app-of-apps, or
ApplicationSet). Each child Application specifies its own workload namespace
independently via its own destination.namespace.
Steps#
Step 1 - Commit and push#
The environments/dev/apps/ Application manifests already exist. Nothing to add.
| |
Step 2 - Apply the root Application#
| |
Step 3 - Verify#
| |
Step 4 - Test: add a service to dev#
Create environments/dev/apps/svc4.yaml (copy svc1.yaml, update names), then:
| |
svc4-dev appears in ArgoCD without any manual kubectl apply.
Step 5 - Test: remove a service from dev#
| |
svc2-dev and its Deployment/Service/Ingress are deleted automatically.
App-of-Apps vs ApplicationSet#
Both patterns solve “how do I manage many Applications without applying each one manually”. They have different strengths:
| App-of-Apps | ApplicationSet | |
|---|---|---|
| Services are homogeneous (same chart) | Overkill | Better fit |
| Services are heterogeneous (mixed charts, sync policies) | Natural fit | Awkward |
| Dev team controls sync policy per app | Yes - they own the Application YAML | No - template is fixed by platform |
| Auto-discovers new services from a values file | No | Yes |
| Bootstrap cost | kubectl apply once per stage | kubectl apply once per AppSet |
For this repo - homogeneous services all using backend-service Helm chart -
the ApplicationSet pattern (see docs/05-applicationset.md) is a better fit for
day-to-day use. App-of-Apps is more appropriate when services differ significantly
or when teams need to own their Application configuration.
Gotchas#
The root Application is not self-managed -
environments/dev/bootstrap.yamlis applied manually viakubectl. Changing it in git has no effect until youkubectl applyagain. The child Applications it generates are git-driven; the root itself is not. Ineu-dev-rancherthis is solved: the root Application watchesplatform/, and the platform Applications (including the AppSet) are all git-managed by ArgoCD. Seedocs/09-sync-waves-cluster-complete.md.destination.namespacemust beargocd- this applies to any Application that manages other Applications, not just app-of-apps. AllApplicationCRDs live in theargocdnamespace regardless of how they were created. A common mistake is settingdestination.namespace: devthinking that’s where child apps should land - ArgoCD will try to createApplicationCRDs indevand nothing will work.prune: trueon the root app cascades - deleting a file fromenvironments/dev/apps/deletes the child Application, which in turn (if it also hasprune: true) deletes all workloads. Intended behaviour, but worth being explicit about.Poll delay - ArgoCD polls git every 3 minutes. Force an immediate sync:
1argocd app sync root-dev