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, [email protected], role:platform-admin
g, [email protected], 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.