GitOps with ArgoCD and ApplicationSets

Once the homelab cluster was running, the first real decision was how to manage everything on top of it. I wanted a GitOps workflow where the cluster could rebuild itself from a repo. ArgoCD was the obvious choice.


The structure

The repo is split into three layers:

gitops/
├── applicationsets/     # How ArgoCD discovers things
│   ├── addons.yaml      # Scans gitops/addons/*/config.json
│   └── apps.yaml        # Scans gitops/apps/*/config.json
├── addons/              # Infrastructure components
│   ├── loki/
│   ├── thanos/
│   ├── envoy-gateway/
│   └── ...
└── apps/                # Business applications

Each addon is a Helm chart wrapper with three files: Chart.yaml, values.yaml, and a config.json that tells ArgoCD the name and namespace.


ApplicationSets for auto-discovery

Instead of creating an ArgoCD Application for each addon manually, I use ApplicationSets. They scan the repo for directories matching a pattern and create Applications automatically.

The addons ApplicationSet looks for any directory under gitops/addons/ that has a config.json. When I add a new addon, I just create the directory, push to Git, and ArgoCD picks it up.

This means deploying a new component is: create a folder, add the chart files, commit, push. No ArgoCD UI needed.


Bootstrap problem

ArgoCD itself needs to be installed before it can manage anything. Same for MetalLB, which provides LoadBalancer IPs in a home network.

I solved this with Terraform. The bootstrap step installs MetalLB and ArgoCD via Helm, then creates a root Application that points to the applicationsets/ directory. From there, ArgoCD takes over and manages everything else, including itself. All with auto-sync enabled. If someone changes something manually in the cluster, ArgoCD reverts it.


The biggest win is confidence. I can destroy the cluster and rebuild it from scratch. The repo is the source of truth, and ArgoCD makes sure the cluster matches it.

The biggest pain was getting the bootstrap order right. Some components depend on others (External Secrets needs to exist before secrets can be synced), and ArgoCD sync waves help with that, but it took some trial and error.