Skip to main content
  1. Posts/
  2. Learning ArgoCD/
  3. Core Patterns/

App-of-Apps Bootstrap Pattern

·783 words·4 mins
Ravi Singh
Author
Ravi Singh
Software engineer with 15+ years building backend systems and cloud platforms across fintech, automotive, and academia. I write about the things I build, debug, and learn — so I don’t forget them.
Learning ArgoCD - This article is part of a series.
Part 3: This Article

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
#

1
2
3
4
5
6
7
8
9
kubectl apply -f environments/dev/bootstrap.yaml
        │  watches environments/dev/apps/
  Application: root-dev
        ├── svc1.yaml  →  Application: svc1-dev  →  Deployment, Service, Ingress in ns: dev
        ├── svc2.yaml  →  Application: svc2-dev  →  Deployment, Service, Ingress in ns: dev
        └── svc3.yaml  →  Application: svc3-dev  →  Deployment, Service, Ingress in ns: dev

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
#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
environments/
  dev/
    bootstrap.yaml        ← root Application; kubectl apply once
    apps/
      svc1.yaml           ← Application: svc1-dev
      svc2.yaml           ← Application: svc2-dev
      svc3.yaml           ← Application: svc3-dev
    values/
      svc1.yaml
      svc2.yaml
      svc3.yaml

The root Application used to live at bootstrap/root-app-dev.yaml (gitignored). It was moved to environments/dev/bootstrap.yaml so all environment bootstrap files live alongside their environment - consistent with the eu-dev-rancher/bootstrap.yaml pattern.


The Root Application
#

environments/dev/bootstrap.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root-dev
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/ravikrs/learning-argocd
    targetRevision: HEAD
    path: environments/dev/apps    # ArgoCD watches this directory
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd              # child Applications land in argocd namespace
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

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.

1
2
3
git add environments/dev/bootstrap.yaml
git commit -m "add app-of-apps root Application for dev"
git push

Step 2 - Apply the root Application
#

1
kubectl apply -f environments/dev/bootstrap.yaml

Step 3 - Verify
#

1
2
3
4
5
6
7
8
9
# Root app should appear and quickly reach Synced / Healthy
kubectl get application root-dev -n argocd

# Child Applications should appear within seconds
kubectl get applications -n argocd

# Detailed view
argocd app get root-dev
argocd app list

Step 4 - Test: add a service to dev
#

Create environments/dev/apps/svc4.yaml (copy svc1.yaml, update names), then:

1
2
3
git add environments/dev/apps/svc4.yaml
git commit -m "add svc4 to dev"
git push

svc4-dev appears in ArgoCD without any manual kubectl apply.

Step 5 - Test: remove a service from dev
#

1
2
3
git rm environments/dev/apps/svc2.yaml
git commit -m "remove svc2 from dev"
git push

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-AppsApplicationSet
Services are homogeneous (same chart)OverkillBetter fit
Services are heterogeneous (mixed charts, sync policies)Natural fitAwkward
Dev team controls sync policy per appYes - they own the Application YAMLNo - template is fixed by platform
Auto-discovers new services from a values fileNoYes
Bootstrap costkubectl apply once per stagekubectl 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.yaml is applied manually via kubectl. Changing it in git has no effect until you kubectl apply again. The child Applications it generates are git-driven; the root itself is not. In eu-dev-rancher this is solved: the root Application watches platform/, and the platform Applications (including the AppSet) are all git-managed by ArgoCD. See docs/09-sync-waves-cluster-complete.md.

  • destination.namespace must be argocd - this applies to any Application that manages other Applications, not just app-of-apps. All Application CRDs live in the argocd namespace regardless of how they were created. A common mistake is setting destination.namespace: dev thinking that’s where child apps should land - ArgoCD will try to create Application CRDs in dev and nothing will work.

  • prune: true on the root app cascades - deleting a file from environments/dev/apps/ deletes the child Application, which in turn (if it also has prune: true) deletes all workloads. Intended behaviour, but worth being explicit about.

  • Poll delay - ArgoCD polls git every 3 minutes. Force an immediate sync:

    1
    
    argocd app sync root-dev
Learning ArgoCD - This article is part of a series.
Part 3: This Article