A production-style GitOps repository that manages multiple Kubernetes clusters across environments and regions using Argo CD App-of-Apps + ApplicationSets — standardized platform addons, self-healing deployments, and per-cluster overlays, all driven from one Git repo.
flowchart TD
Dev[Engineer] -->|git push / PR| Repo[(Git: argocd-gitops-platform)]
Repo -->|polls & reconciles| ArgoCD[Argo CD]
subgraph Control["Argo CD (management cluster)"]
ArgoCD --> Root[root app-of-apps]
Root --> Projects[AppProjects]
Root --> AddonsApp[addons Application]
Root --> WorkloadsApp[workloads Application]
Root --> PlatformCfg[platform-config Application]
AddonsApp --> AddonsAS{{addons ApplicationSet}}
WorkloadsApp --> WorkloadsAS{{sample-api ApplicationSet}}
end
AddonsAS -->|matrix: clusters x addons| C1
AddonsAS --> C2
AddonsAS --> C3
WorkloadsAS -->|cluster generator| C1
WorkloadsAS --> C2
WorkloadsAS --> C3
subgraph Fleet["Managed clusters"]
C1[dev-eu]
C2[stg-eu]
C3[prod-eu]
end
The root Application is the only thing applied by hand. Everything below it — projects, platform addons, and workloads — is rendered declaratively and kept in sync (and self-healed) by Argo CD. Adding a cluster or an addon is a pull request.
- App-of-Apps bootstrap — a single root Application owns the entire platform tree.
- ApplicationSets for fan-out — a matrix generator crosses clusters with addons; a cluster generator rolls workloads onto every environment automatically.
- Multi-cluster, multi-region —
dev-eu,stg-eu,prod-eumanaged from one repo, selected by cluster-secret labels. - Standardized platform addons — ingress-nginx, cert-manager, external-secrets, metrics-server, and kube-prometheus-stack, each with per-cluster value overlays.
- Both packaging models demonstrated —
sample-apishipped as a Helm chart and as a Kustomize base + overlays. - Sync waves — deterministic ordering: projects → addon CRDs/charts → platform config → workloads.
- Progressive delivery —
RollingSyncpromotes addon changes dev → stg → prod. - Secret-safe — zero plaintext secrets; everything sensitive flows through External Secrets.
- CI validation — yamllint,
helm lint/template,kustomize build, andkubeconformschema checks on every PR.
argocd-gitops-platform/
├── bootstrap/ # Install Argo CD + apply the root app
│ ├── argocd-values.yaml # Helm values for the argo-cd chart (HA)
│ └── root-app-of-apps.yaml # The one Application you apply by hand
├── apps/ # App-of-apps children (one per concern)
│ ├── 00-projects.yaml
│ ├── 10-addons-appset.yaml
│ ├── 20-workloads-appset.yaml
│ └── 30-platform-config.yaml
├── applicationsets/ # Fan-out generators
│ ├── addons-appset.yaml # matrix: clusters x addons
│ └── workloads-appset.yaml # cluster generator -> sample-api
├── addons/ # Platform addons (Helm + per-cluster values)
│ ├── ingress-nginx/values/{common,dev-eu,stg-eu,prod-eu}.yaml
│ ├── cert-manager/{clusterissuers.yaml,values/...}
│ ├── external-secrets/{clustersecretstore.yaml,values/...}
│ ├── metrics-server/values/...
│ └── kube-prometheus-stack/values/...
├── workloads/
│ └── sample-api/
│ ├── chart/ # Helm packaging
│ └── kustomize/ # base + overlays/{dev,stg,prod}
├── clusters/ # Cluster registration placeholders (no secrets)
│ ├── dev-eu/cluster.yaml
│ ├── stg-eu/cluster.yaml
│ └── prod-eu/cluster.yaml
├── projects/ # AppProjects (RBAC + allowed sources/dests)
│ ├── platform.yaml
│ └── workloads.yaml
└── .github/workflows/ci.yml # Validation pipeline
# 1. Install Argo CD into the management cluster (HA values).
helm repo add argo https://argoproj.github.io/argo-helm
helm upgrade --install argocd argo/argo-cd \
--namespace argocd --create-namespace \
--version 7.7.x \
-f bootstrap/argocd-values.yaml
# 2. Register the managed clusters (creates labelled cluster secrets).
# Prefer `argocd cluster add`; never commit real credentials.
argocd cluster add dev-eu --label env=dev --label addons=enabled --label workloads=enabled
argocd cluster add stg-eu --label env=stg --label addons=enabled --label workloads=enabled
argocd cluster add prod-eu --label env=prod --label addons=enabled --label workloads=enabled
# 3. Apply the ONE root Application — everything else flows from Git.
kubectl apply -f bootstrap/root-app-of-apps.yaml
# 4. Watch it converge.
argocd app list
argocd app get rootFrom here on, all changes are pull requests against this repo.
bootstrap/root-app-of-apps.yaml points Argo CD at apps/, which holds one child
Application per platform concern (projects, the addons ApplicationSet wrapper, the
workloads ApplicationSet wrapper, and standalone platform config). The root owns the
whole tree, so the platform is reproducible from an empty cluster + this repo.
addons-appset.yamluses a matrix generator ofclusters × addons. The cluster generator selects clusters labelledaddons=enabled; the list generator carries each addon's upstream chart coordinates. The result is one Application per(cluster, addon)— e.g.prod-eu-ingress-nginx. Per-cluster values are layered asaddons/<addon>/values/common.yaml+addons/<addon>/values/<cluster>.yaml.workloads-appset.yamluses a cluster generator and selects the Kustomize overlay from the cluster'senvlabel, so the same base manifests promote across dev → stg → prod without copy/paste.
Ordering is enforced with argocd.argoproj.io/sync-wave:
| Wave | What |
|---|---|
-10 |
root app-of-apps |
-5 |
AppProjects |
-3 |
core addons (ingress, cert-manager, ESO, metrics) |
-1 |
platform config (ClusterIssuers, SecretStore) + monitoring |
0 |
workloads ApplicationSet wrapper |
1 |
sample-api workloads |
Every Application sets syncPolicy.automated with prune: true and selfHeal: true,
plus exponential retry. Manual drift on a cluster is reverted to the Git state
automatically; deleted-from-Git resources are pruned.
No plaintext secrets are ever committed. Sensitive material flows through the External Secrets Operator:
- A
ClusterSecretStore(addons/external-secrets/clustersecretstore.yaml) connects ESO to the backing secrets manager (AWS Secrets Manager shown; swap for Vault, GCP Secret Manager, or Azure Key Vault). Authentication uses IRSA / Workload Identity — no static credentials. - Workloads declare an
ExternalSecret(see the Helm chart and the Kustomize base) referencing remote keys likesample-api/database-url. - ESO materializes a Kubernetes
Secretin-cluster, consumed viaenvFrom.
cert-manager ACME account keys, Grafana admin credentials, and Argo CD SSO secrets
follow the same pattern. .gitignore additionally blocks *.pem, *-secret.yaml,
.env, kubeconfigs, and similar. For air-gapped setups, the same manifests work with
SealedSecrets instead of ESO.
Problem. Operating a fleet of Kubernetes clusters (EKS / ROSA / RKE2-style) across
multiple environments and regions tends toward snowflakes: addon versions drift between
clusters, secrets get pasted into manifests, and "what is actually running where?"
becomes unanswerable. Manual kubectl/helm runs don't scale and aren't auditable.
Approach. Treat the fleet as one declarative system in Git:
- One source of truth. The root app-of-apps means a cluster's entire platform is reconstructable from this repo. Reviews and history give a full audit trail.
- Fan-out, not copy-paste. ApplicationSets generate per-cluster Applications from generators, so onboarding a new cluster is labelling its secret — the full addon stack and workloads roll out automatically.
- Standardized addons, local overrides. Each addon has a
common.yamlbaseline with thin per-cluster overlays (replica counts, retention, HPA bounds, ACME issuer), keeping clusters consistent while honoring environment differences. - Safe rollouts.
RollingSyncpromotes addon changes dev → stg → prod; sync waves guarantee CRDs and issuers exist before the resources that depend on them. - Self-healing & drift control. Automated sync with prune + selfHeal converges any cluster back to Git, eliminating configuration drift.
- Secret hygiene. External Secrets keeps credentials out of Git entirely while still letting workloads declare exactly which secrets they need.
Outcome. Adding a cluster or bumping an addon is a reviewable pull request, every cluster's state is knowable from Git, and production changes are gated behind earlier environments — the operational properties expected of a senior SRE/platform setup.
(Generic by design — no employer, client, or proprietary names. Placeholders only:
example.com, acme, dev-eu/stg-eu/prod-eu.)
Released under the MIT License. Copyright © 2026 Muhammad Imad.