Managing multiple Kubernetes clusters and applications can get complex fast. GitOps helps tame this complexity—and the App of Apps pattern takes it to the next level with declarative, scalable, and automated infrastructure management.
GitOps is a DevOps operating model where Git is the single source of truth for declarative infrastructure and applications. Tools like Argo CD sync the state of your Kubernetes clusters to match Git, automatically and continuously.
The App of Apps pattern uses a single Argo CD Application to manage many other Argo CD Applications. It enables modular, scalable, and environment-specific deployment structures.
Imagine one app (root-app.yaml) that deploys:
- Platform apps like Ingress, Cert-Manager & Operators
- Workload apps like Podinfo, Guestbook, etc.
Each app lives in its own folder, can use Kustomize/Helm, and is deployed declaratively from Git.
It offers:
- Declarative control : Everything is defined in Git.
- Zero-touch provisioning : GitOps installs and configures your entire stack.
- Environment-specific overlays : Adapt configurations for K3s, OpenShift, Dev, Prod etc.
- Disaster recovery : Rebuild any where
- Auditable changes : Every change is a Git commit.
- No drift : GitOps continuously reconciles desired vs. actual state.
- Self Healing : Accidently deleted something ? Let GitOps fix it for you.
Start the timer
- A Kubernetes cluster: This demo is tested on
K3sbut should work on any cluster - CLI tools :
kubectl,helm - Forked git repo :
git clone https://github.com/arslankhanali/GitOps-App-of-Apps-Pattern.git`
Now! start the timer
export KUBECONFIG=~/k3s-config # <-- To access Kubernetes cluster
# kubectl get all -A
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
kubectl create namespace argocd
helm install argocd argo/argo-cd -n argocd -f argocd/values.yamlApply environment-specific ingress for argocd :
# K3s
kubectl apply -f argocd/ingress.yaml
# OpenShift
kubectl apply -f argocd/route.yamlMake sure your /etc/hosts file has following entries.
# sudo vim /etc/hosts
<K3s-cluster-IP> k3s.node1 argocd.node1 test.node1 hello.node1
To see apps getting deployed.
- Argocd argocd.node1
# Get Login password for admin user
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -dThis points to k3s right now
kubectl apply -f root-app.yaml- Kubernetes Dashboard k3s.node1
# Get Bearer Token
kubectl get secret admin-user -n kubernetes-dashboard -o jsonpath="{.data.token}" | base64 -d
- Guestbook test.node1
- Podinfo hello.node1
You can now stop the Timer. It tooks me < 1min to deploy everything.
- Synced the
env/{k3s}/directory. - Created child applications in {platform & workloads} folders.
- Deployed all components declaratively.
This pattern allows full cluster rebuilds and updates via Git commits alone.

- Add application to the
apps/folder. - Test the application
kustomize build <kustomization-dir> | kubectl apply -f -
# or
kubectl apply -k k8s/overlays/dev
- Create argocd-application in
env/{k3s}folder
See example: argoproj Application'
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: <app-name> # <-- CHANGE THIS as needed
namespace: argocd # <-- NEVER CHANGE
spec:
project: default
source:
repoURL: git@github.com:arslankhanali/homelab-kubernetes.git
path: apps/<app-name>/overlays/k3s # <-- CHANGE THIS as needed
targetRevision: HEAD
destination:
server: https://kubernetes.default.svc
namespace: <app-name> # <-- CHANGE THIS as needed
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true # If namespace named above should be created- Reference file you created in step3 in
/env/k3s/kustomization.yaml - Push to git
git add . && git commit -m "new app" && git push - Argo should sync automatically
kubectl delete -f root-app.yaml
# delete all argocd apps
for app in $(kubectl get applications -n argocd -o jsonpath='{.items[*].metadata.name}'); do
kubectl patch application "$app" -n argocd -p '{"metadata":{"finalizers":[]}}' --type=merge
kubectl delete application "$app" -n argocd --force --grace-period=0
done
kubectl delete ns argocd
kubectl delete ns kubernetes-dashboard
kubectl delete ns podinfo
kubectl delete ns guestbookThe ArgoCD App of Apps pattern offers a scalable, Git-driven blueprint for managing Kubernetes clusters :
- Manage everything declaratively in Git
- Scale across environments like K3s and OpenShift
- Rebuild or recover your clusters on demand
The App of Apps pattern isn't just a tool—it's a mindset shift for cloud-native GitOps. Adopt it to bring structure, repeatability, and security to your infrastructure.
├── apps # Apps & workload YAMLS, Helm charts or Kustomize can go here
│ ├── guestbook # Sample App from https://github.com/argoproj/argocd-example-apps/tree/master/kustomize-guestbook
│ │ ├── base
│ │ └── overlays
│ ├── kubernetes-dashboard # Upstream K8s dashboard https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/
│ │ ├── base
│ │ └── overlays
│ └── podinfo # Sample App from https://github.com/stefanprodan/podinfo/tree/master/kustomize
│ ├── base
│ └── overlays
├── env # ArgoCD Applications - Folders can be Cluster-specific (k3s,openshift) or Env Specific (dev,
│ ├── k3s
│ │ ├── kustomization.yaml
│ │ ├── platform
│ │ └── workloads
│ └── openshift
│ ├── kustomization.yaml
│ ├── platform
│ └── workloads
├── ingress.yaml # Ingress to access ArgoCD dashboard
├── README.md
├── root-app.yaml # Root ARGOCD application
└── values.yaml # Deploy Argo with insecure access (needed for Ingress) & enable Helm for kustomizeI have 3 apps here as an example :
- guestbook : Kustomize based app argocd-kustomize-guestbook
- kubernetes-dashboard/ : Kustomize calls Helm to install K8s dashboard for K3s.
- podinfo : Kustomize based app stefanprodan-podinfo
You can use YAML manifests, kustomize or Helm charts to add more applications in this folder.
Each app follows :
apps/
└── <app1>/
├── base/
└── overlays/
├── <env1-name>/ # <--- Can Change Name - e.g. DEV
└── <env2-name>/ # <--- Can Change Name - e.g. PROD"ArgoCD Application" definitions for different environments. They basically call different overlays in apps.
- env/k3s/ : Deploys K8s Dashboard and uses
Ingressfor apps - env/openshift/ : No K8s Dashboard and uses
Routefor apps
Each env follows :
── env
│ ├── <env1-name>
│ │ ├── kustomization.yaml
│ │ ├── platform # <--- Can Change Name - Just used to categorise `argocd-application`
│ │ │ └── 'argocd-application-for-app1'.yaml
│ │ └── workloads # <--- Can Change Name - Just used to categorise `argocd-application`
│ │ ├── 'argocd-application-for-app2'.yaml
│ │ └── 'argocd-application-for-app3'.yamlMain reason this pattern is called
APP OF APPS.
This top-level ArgoCD Application points to env/{k3s} and deploys all children ArgoCD Application in it.

