Git Gets the Keys

August 18, 2026

Until now, changing my cluster meant connecting to it and running a command. I could inspect the result, but I wanted a record of the configuration I intended to run.

For the next phase, I'd keep that configuration in Git.

Why Flux?

The two names I encountered most often when reading about Kubernetes GitOps were Flux and Argo CD. Both continuously compare a declared state with the state of a cluster, but they present that workflow differently.

Argo CD provides a prominent web interface as well as a CLI. Flux is more CLI- and Kubernetes-API-first and does not ship with an equivalent central dashboard. The community learning path starts with Flux so that I work directly with Git, manifests and reconciliation rather than learning the workflow mainly through a UI.

That does not make Flux universally better, or Argo CD something I can ignore. It simply makes Flux a useful first implementation of GitOps because fewer details are hidden from me.

Flux also has a place in Azure environments. Microsoft provides Flux v2 as the microsoft.flux cluster extension for both Azure Kubernetes Service and Azure Arc-enabled Kubernetes. That integration interested me, though Flux works outside Azure and Argo CD can run there too.

Preparing the repository

I created a personal GitHub repository named zbook-cluster. My local Git installation initially created its branch as master, so I renamed it before bootstrapping Flux and changed the default for future repositories:

git branch -m main
git config --global init.defaultBranch main

The repository will hold declarative cluster configuration rather than a copy of the cluster itself. Kubeconfigs, private keys, cluster tokens and unencrypted secrets do not belong in it.

Bootstrapping Flux

With the Flux CLI installed on my MacBook and its kubeconfig pointing to the K3s API on the ZBook, I ran:

flux bootstrap github \
  --owner=$GITHUB_USER \
  --repository=zbook-cluster \
  --branch=main \
  --path=./clusters/staging \
  --personal

The options make the intended relationship explicit:

  • --owner selects my GitHub account without hard-coding it into the command.
  • --repository identifies the repository that will hold the cluster state.
  • --branch=main tells Flux which branch to reconcile.
  • --path=./clusters/staging gives this cluster its own configuration path.
  • --personal identifies the owner as a personal account rather than an organisation.

The command did considerably more than install a local CLI. It committed the Flux manifests to GitHub, installed the Flux components into the cluster, configured repository access and created the objects that tell Flux to reconcile the selected repository path.

The bootstrap completed with four controller deployments ready:

✔ source-controller: deployment ready
✔ kustomize-controller: deployment ready
✔ helm-controller: deployment ready
✔ notification-controller: deployment ready

Each has a focused responsibility. The Source Controller retrieves declared configuration. The Kustomize Controller builds and applies Kubernetes resources. The Helm Controller manages declarative Helm releases. The Notification Controller handles events and integrations.

Two Helm controllers, one important boundary

The helm-controller line initially looked familiar because K3s already includes something with almost the same name. They are not the same component.

K3s's controller reconciles HelmChart resources from the helm.cattle.io API group and currently manages the packaged Traefik installation in my cluster. Flux's controller runs as a deployment in flux-system and reconciles HelmRelease resources from helm.toolkit.fluxcd.io.

They can coexist because they watch different APIs. In this lab, K3s owns its packaged components, while Flux will own Helm releases declared through my GitOps repository. I need to keep those responsibilities separate so the controllers don't try to manage the same release.

GitOps is installed, but not yet proven

The controllers are running and the repository connection exists, but I have not yet demonstrated the most interesting part: changing a workload in Git and watching Flux reconcile the cluster without an imperative deployment command.

For now, the checkpoint is straightforward:

  • The zbook-cluster repository uses main.
  • Flux watches clusters/staging.
  • The default Flux controllers are running in the K3s cluster.
  • Git now contains the machinery that keeps the GitOps installation in place.
  • No application workload has been handed over to Flux yet.

The next test is an application deployment: commit its manifests and check whether Flux creates the workload.