At this point you have a working Kubernetes cluster but it’s empty. You need networking so pods can actually talk to each other. You need ingress so services are accessible. You need storage for stateful apps. And you want to run actual workloads.
I use Flux CD to manage all of that. It’s a GitOps tool. Everything lives in Git, and Flux makes sure your cluster matches what’s in the repo. You edit Git, commit, push, and the cluster updates automatically.
The idea behind GitOps#
Declare your infrastructure in Git. A controller running in your cluster watches the repo and reconciles reality to match what it sees. Want to deploy an app? Add a HelmRelease to Git and commit. Want to update a config? Edit the file and push. Flux applies it.
The payoff is huge. Every infrastructure change is a Git commit, so you have complete audit history. You can revert by reverting commits. You can test changes in a branch before merging. And your cluster state is always documented in code.
How I structured it#
Two layers. Infrastructure layer has the cluster plumbing: Traefik for ingress, MetalLB for load balancing, cert-manager for certificates, Prometheus and Grafana for monitoring. Applications layer has Homarr for a dashboard, Nextcloud for file sync, and whatever else I want to run.
Both layers are managed by Flux. The infrastructure layer runs in the background keeping the cluster working. The applications layer is where my actual workloads run.
The directory structure mirrors this:
gitops/clusters/totorolab/
├── flux-system/
├── infrastructure/
│ └── base/
├── apps/
│ └── base/
│ ├── homarr/
│ ├── nextcloud/
│ └── monitoring/Setting up Flux#
Install it:
flux installThen point it at your Git repo:
flux create source git flux-system \
--url=https://github.com/lolverae/homelab \
--branch=main \
--path=./gitops/clusters/totorolabFlux watches that repo. Every 10 minutes it checks for changes. When it sees something new, it applies it.
Check what’s happening:
flux get all
flux get sources
flux get kustomizationsAdding applications#
Write a HelmRelease in your repo:
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: my-app
namespace: default
spec:
chart:
spec:
chart: my-chart
sourceRef:
kind: HelmRepository
name: my-repo
values:
replicas: 2
image:
tag: v1.0.0Commit and push. Flux creates it automatically. To update, edit the values, commit, and Flux applies the changes.
Secrets#
Database passwords and API keys need to be encrypted. I use SOPS to encrypt them in Git. When Flux applies them, it decrypts and creates Kubernetes secrets. This way your entire cluster config is in Git and backed up, but secrets stay secure.
What I got wrong#
First time I set this up, I committed secrets in plain text to Git. I caught it quickly but learned the lesson. Now I use SOPS from the start.
Also, I initially didn’t version control the cluster config. If something broke, I had to remember what I’d manually changed. Now everything is in Git, so I can always see what the cluster should look like.
Why this matters#
Your cluster becomes reproducible. If the entire thing dies, you clone the repo and point Flux at it and it rebuilds itself. Changes are auditable. Infrastructure is code, reviewable like any software project.
That’s the real power of GitOps.