GitOps with Flux: Letting Git Be Your Cluster's Source of Truth
Stop running kubectl apply and let the cluster pull its own config

There’s a particular kind of dread that comes from not knowing what’s actually running in your cluster. You applied a manifest weeks ago, then patched something live to fix an outage, then someone else tweaked a config map, and now the YAML in your repo and the reality in the cluster have quietly diverged. Nobody can say with confidence what’s deployed, which means nobody can rebuild it from scratch. That drift is the enemy, and GitOps is the cure.
The idea is simple to the point of seeming obvious once you’ve heard it: a Git repository is the single source of truth for what should be running, and an agent inside the cluster continuously reconciles reality to match it. You stop pushing changes to the cluster. Instead you push to Git, and the cluster pulls. Flux is the tool I reach for to make that happen.
1 Push versus pull, and why pull wins
The old way is push: your laptop or CI pipeline runs kubectl apply against the cluster. That means credentials with write access to production sit in your CI system, and any drift between runs goes unnoticed until something breaks.
Flux inverts it. A controller runs inside the cluster, watches a Git repository, and applies whatever it finds there — every few minutes, forever. Three consequences fall out of this. First, no external system needs cluster credentials; the cluster reaches out, not in. Second, drift self-heals: if someone hand-edits a live resource, Flux notices it no longer matches Git and reverts it on the next reconcile. Third, your Git history is your deployment history. Want to roll back? git revert and wait sixty seconds.
2 Getting it bootstrapped
Flux installs itself into a cluster and, neatly, commits its own manifests to your repo so that Flux is itself managed by GitOps. The CLI does the heavy lifting:
flux bootstrap github \
--owner=my-username \
--repository=homelab-cluster \
--branch=main \
--path=./clusters/home \
--personalThat creates a deploy key, installs the controllers, and writes Flux’s own config under clusters/home/. From this moment, the repo runs the cluster.
3 Telling Flux what to watch
Flux’s model is two kinds of object. A source says where to find config — a Git repo, a Helm repo, or an S3 bucket. A Kustomization (or HelmRelease) says what to do with it. Here’s a source pointing at an app repo:
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: my-app
namespace: flux-system
spec:
interval: 1m
url: https://github.com/my-username/my-app
ref:
branch: main
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: my-app
namespace: flux-system
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: my-app
path: "./deploy"
prune: trueThat prune: true is the quiet hero. When you delete a manifest from Git, Flux deletes the corresponding resource from the cluster. No orphaned objects lingering because someone forgot to kubectl delete them. The repo is genuinely the truth, additions and removals alike.
You can watch reconciliation happen, or force it when you’re impatient:
flux get kustomizations
flux reconcile kustomization my-app --with-source4 The honest friction
GitOps is not free of sharp edges. The biggest is secrets. You absolutely cannot commit plaintext secrets to Git, so you need SOPS with an age or KMS key, or the Sealed Secrets controller, to encrypt them before they land in the repo. Setting that up is the first real hurdle, and it’s a non-negotiable one.
The second is the debugging shift. When a deploy fails, the error isn’t on your terminal — it’s buried in a controller’s status. You learn to read flux get output and kubectl describe kustomization instead of watching apply scroll past. It’s a different reflex, and it takes a week to feel natural.
The third is discipline. GitOps only works if everyone stops touching the cluster directly. The first time someone fixes an outage with a live kubectl edit, Flux will cheerfully revert their fix on the next reconcile, which is either exactly what you wanted or a baffling regression depending on whether the team has bought in.
5 The verdict
For a serious cluster — anything you’d be upset to lose and have to rebuild from memory — GitOps with Flux is worth every hour of setup. The payoff is enormous: a cluster you can recreate from an empty machine and a Git URL, a deployment history you can audit, and drift that fixes itself. I run it at home precisely because I forget what I did six months ago, and Flux remembers for me.
For a throwaway test cluster you’ll delete on Friday, it’s overkill; just kubectl apply and move on. But the moment a cluster crosses from “experiment” to “thing I depend on,” let Git be the truth. Your future self, staring at a dead node and wondering what was supposed to be running, will thank you.




