Production-ready GitOps bootstrap using the ArgoCD App of Apps pattern. One command installs ArgoCD; from that point the cluster is fully GitOps-driven — every tool, every environment, every config change flows through Git.
📐 Edit in Excalidraw — open the
.excalidrawfile at excalidraw.com to edit interactively.
Git Repository
├── bootstrap/ ──► ArgoCD (root Application)
│ └── root-app.yaml │
├── platform/ │ manages
│ ├── argocd/ ◄──────┤
│ ├── cert-manager/ ◄─────┤ App of Apps
│ └── ingress-nginx/ ◄────┘
└── apps/
├── dev/ ──► dev cluster namespace
├── staging/ ──► staging cluster namespace
└── prod/ ──► prod cluster namespace
ArgoCD ──► Sync ──► Kubernetes API ──► Deployed Resources
└──► Notifications ──► Slack / Email
- The App of Apps Pattern
- Repository Structure
- Prerequisites
- Bootstrap (Quick Start)
- Platform Tools
- Multi-Environment Applications
- ApplicationSets
- RBAC — Multi-Tenant Setup
- Slack Notifications
- Promoting Across Environments
- Disaster Recovery
- Troubleshooting
Standard ArgoCD installations require someone to manually create each Application object. The App of Apps pattern solves this: you create one root Application that points to a directory of other Application manifests. ArgoCD then self-manages itself and every tool from Git.
kubectl apply -f bootstrap/root-app.yaml
# → ArgoCD syncs root-app.yaml
# → root-app discovers platform/ directory
# → platform apps deploy: ArgoCD itself, cert-manager, ingress-nginx
# → apps/ ApplicationSets create per-environment Applications
# Everything else is automated
This means:
- New cluster?
kubectl apply -f bootstrap/root-app.yamland walk away - New environment? Add a directory under
apps/and push - New platform tool? Add an Application under
platform/and push
argocd-gitops-platform/
├── bootstrap/
│ └── root-app.yaml # The single apply that starts everything
├── platform/ # Platform-level Applications
│ ├── argocd/
│ │ ├── application.yaml # ArgoCD self-manages via GitOps
│ │ └── values.yaml # ArgoCD Helm values (RBAC, ingress, etc.)
│ ├── cert-manager/
│ │ ├── application.yaml
│ │ └── values.yaml
│ └── ingress-nginx/
│ ├── application.yaml
│ └── values.yaml
├── apps/
│ ├── dev/
│ │ └── applicationset.yaml # ApplicationSet generating dev Applications
│ ├── staging/
│ │ └── applicationset.yaml
│ └── prod/
│ └── applicationset.yaml
├── workloads/ # Application Helm chart definitions
│ └── <app-name>/
│ ├── Chart.yaml
│ └── values/
│ ├── dev.yaml
│ ├── staging.yaml
│ └── prod.yaml
├── notifications/
│ └── slack-config.yaml # Slack notification templates
├── rbac/
│ └── policy.csv # ArgoCD RBAC policy (multi-tenant)
└── scripts/
└── bootstrap.sh # Idempotent one-command cluster bootstrap
| Tool | Version | Install |
|---|---|---|
| kubectl | ≥ 1.28 | brew install kubectl |
| helm | ≥ 3.12 | brew install helm |
| argocd CLI | ≥ 2.9 | brew install argocd |
| A running K8s cluster | GKE / EKS / kind / k3s | — |
# 1. Clone
git clone https://github.com/ashiq-ali/argocd-gitops-platform
cd argocd-gitops-platform
# 2. Fork this repo and update the repoURL in bootstrap/root-app.yaml
# Change: repoURL: https://github.com/ashiq-ali/argocd-gitops-platform
# To: repoURL: https://github.com/<YOUR_ORG>/argocd-gitops-platform
# 3. Install ArgoCD (only needed the first time)
kubectl create namespace argocd
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd \
--namespace argocd \
--version 6.7.3 \
--wait
# 4. Apply the root Application — this is the ONLY manual step
kubectl apply -f bootstrap/root-app.yaml
# 5. Wait for everything to sync
kubectl -n argocd get applications -w
# 6. Get the initial admin password
argocd admin initial-password -n argocd
# 7. Access the UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Open https://localhost:8080Or use the idempotent bootstrap script:
./scripts/bootstrap.sh \
--repo-url https://github.com/YOUR_ORG/argocd-gitops-platform \
--cluster-name productionThree platform tools are managed via GitOps from day 1:
ArgoCD manages itself via platform/argocd/application.yaml. Upgrades happen through Git:
# Upgrade ArgoCD: edit the targetRevision in platform/argocd/values.yaml
git commit -m "chore: upgrade argocd to 2.11.0"
git push
# ArgoCD detects the change and self-upgradesAutomatically provisions TLS certificates from Let's Encrypt for all Ingress resources annotated with:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prodHandles external HTTP/HTTPS traffic. After bootstrap, create Ingress resources normally — they'll get a real IP/hostname from the cloud load balancer.
Each environment uses an ApplicationSet to generate Applications from a directory structure:
# apps/dev/applicationset.yaml
spec:
generators:
- git:
repoURL: https://github.com/YOUR_ORG/argocd-gitops-platform
revision: main
directories:
- path: workloads/*
template:
spec:
destination:
namespace: '{{path.basename}}-dev'
helm:
valueFiles:
- values/dev.yamlTo add a new application to all environments:
mkdir -p workloads/my-new-app/values
cat > workloads/my-new-app/Chart.yaml << EOF
apiVersion: v2
name: my-new-app
version: 0.1.0
dependencies:
- name: my-new-app
version: "1.0.0"
repository: "https://charts.myorg.io"
EOF
cat > workloads/my-new-app/values/dev.yaml << EOF
my-new-app:
replicaCount: 1
image.tag: dev-latest
EOF
git add workloads/my-new-app
git commit -m "feat: add my-new-app to all environments"
git push
# ArgoCD picks it up within 3 minutes (default poll interval)ApplicationSets automate Application creation across environments. This repo uses the Git Directory generator which creates one Application per subdirectory:
workloads/
├── api-service/ → Application: api-service-dev, api-service-staging, api-service-prod
├── worker/ → Application: worker-dev, worker-staging, worker-prod
└── frontend/ → Application: frontend-dev, frontend-staging, frontend-prod
Each ApplicationSet also sets automated sync and self-heal so drift is corrected automatically:
syncPolicy:
automated:
prune: true # Remove resources deleted from Git
selfHeal: true # Revert manual kubectl changesMulti-tenant RBAC is defined in rbac/policy.csv:
# Platform team — admin on everything
p, role:platform-admin, applications, *, */*, allow
p, role:platform-admin, clusters, *, *, allow
# Dev team — read-only on prod, full on dev/staging
p, role:dev-team, applications, get, */*, allow
p, role:dev-team, applications, *, *-dev/*, allow
p, role:dev-team, applications, *, *-staging/*, allow
# Read-only (auditors)
p, role:read-only, applications, get, */*, allow
p, role:read-only, logs, get, */*, allow
# Group mappings (SSO via Okta/Google)
g, platform-team@company.com, role:platform-admin
g, dev-team@company.com, role:dev-team
Configure Slack alerts for sync events, health changes, and deployment completions:
# 1. Create a Slack webhook secret
kubectl create secret generic argocd-notifications-secret \
-n argocd \
--from-literal=slack-token=xoxb-YOUR-SLACK-BOT-TOKEN
# 2. Annotate Applications for notifications
kubectl annotate application my-app \
notifications.argoproj.io/subscribe.on-sync-succeeded.slack=deployments-channel \
notifications.argoproj.io/subscribe.on-health-degraded.slack=alerts-channelNotification templates in notifications/slack-config.yaml include deployment summaries with app name, sync revision, and diff link.
The recommended promotion flow — no branch-per-environment:
# 1. Deploy to dev (automatic on merge to main)
git merge feature/my-change
git push origin main
# 2. Validate in dev, then promote to staging
# Edit workloads/my-app/values/staging.yaml
git commit -m "chore: promote my-app v1.2.3 to staging"
git push
# 3. After staging sign-off, promote to prod
# Edit workloads/my-app/values/prod.yaml
git commit -m "chore: promote my-app v1.2.3 to prod"
git push
# ArgoCD syncs within 3 minutes; Slack notification confirmsFor urgent prod rollbacks:
# Revert the last commit
git revert HEAD
git push
# ArgoCD self-heals within secondsFull cluster recovery from scratch:
# New cluster, same Git repo
kubectl create namespace argocd
helm install argocd argo/argo-cd --namespace argocd --wait
kubectl apply -f bootstrap/root-app.yaml
# All platform tools and applications restore from Git automatically
# Recovery time: ~10 minutes for platform tools, then app pods come upThe only state outside Git is:
- TLS certificates (cert-manager re-issues automatically)
- Application secrets (restore from your secrets manager — External Secrets Operator recommended)
Application stuck in OutOfSync forever
argocd app diff my-app # See what's different
argocd app sync my-app --force # Force sync
# If still stuck, check for Helm rendering errors:
argocd app manifests my-appComparisonError: failed to load target state
Usually a Helm chart values error. Check:
argocd app logs my-app
helm template workloads/my-app --values workloads/my-app/values/dev.yamlNotifications not arriving
kubectl logs -n argocd deployment/argocd-notifications-controller | grep -i slack
# Verify the token in the secret is validArgoCD UI unreachable after upgrade
ArgoCD self-upgrade can briefly disrupt the UI. Wait 2 minutes and refresh. If still down:
kubectl rollout restart deployment/argocd-server -n argocdBuilt to demonstrate production GitOps workflows from hands-on ArgoCD experience at Virgin Media O2.