Flux GitOps: Reconciliation Loops and Why They Change Everything

Flux GitOps: Reconciliation Loops and Why They Change Everything

There’s a moment when you first truly understand GitOps that feels like a cognitive shift. You stop thinking about deploying software and start thinking about declaring the desired state of your infrastructure, then trusting a controller to make reality match that declaration.

Flux is the tool that gave me that moment. Not because it’s magic—it isn’t—but because it implements the reconciliation loop so cleanly that the mental model becomes inescapable.

The Core Concept: Git as the Source of Truth

The premise of Flux is simple: your Git repository is the single source of truth for what should be running in your Kubernetes cluster. Flux watches your repo. When the repo changes, Flux makes the cluster match. When someone makes a change directly to the cluster (either accidentally or “just this once”), Flux notices the drift and corrects it.

This isn’t just convenient. It fundamentally changes your relationship with your infrastructure:

  • Audit trail is free: Every change to your cluster went through a git commit. Who changed what, when, why—it’s all there.
  • Rollback is git revert: Broke something? Revert the commit. Flux will reconcile back to the previous state.
  • Consistency across clusters: The same repo deployed to dev, staging, and production means they’re actually running the same configuration.

Flux Architecture

Flux v2 (the current version) is built as a set of Kubernetes controllers, each with a specific responsibility:

Source Controller: Watches for changes in Git repos, Helm repositories, OCI registries, and S3-compatible buckets. When a source changes, it fetches the new content and makes it available to other controllers.

Kustomize Controller: Applies Kustomize manifests from sources. This is how plain Kubernetes manifests get applied.

Helm Controller: Manages Helm releases. It watches HelmRelease objects and ensures the corresponding Helm release is installed, upgraded, or rolled back as needed.

Notification Controller: Sends alerts to Slack, Teams, PagerDuty, etc. when reconciliation succeeds or fails. Also receives webhooks to trigger immediate reconciliation.

Image Automation Controllers: Watches container registries for new image tags and can automatically update your Git repo with new image versions.

A Real-World HelmRelease

Here’s what deploying an application with Flux actually looks like:

apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
  name: ingress-nginx
  namespace: flux-system
spec:
  interval: 1h
  url: https://kubernetes.github.io/ingress-nginx
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
spec:
  interval: 30m
  chart:
    spec:
      chart: ingress-nginx
      version: ">=4.0.0 <5.0.0"
      sourceRef:
        kind: HelmRepository
        name: ingress-nginx
        namespace: flux-system
  values:
    controller:
      replicaCount: 2
      service:
        type: LoadBalancer
  install:
    remediation:
      retries: 3
  upgrade:
    remediation:
      retries: 3
      remediateLastFailure: true
    cleanupOnFail: true

That HelmRelease object is the entire deployment configuration for ingress-nginx. Flux handles fetching the chart, installing it, watching for new versions (within the semver constraint), and remediating failed upgrades.

Dependency Management

One of the most powerful features of Flux is dependency management between HelmReleases. When you have infrastructure that must start in a specific order, dependsOn handles it:

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: cert-manager
  namespace: cert-manager
spec:
  interval: 30m
  dependsOn:
    - name: ingress-nginx
      namespace: ingress-nginx
  # ...

Cert-manager won’t be reconciled until ingress-nginx is Ready. This sounds simple, but it solves an enormously painful real-world problem: what happens when you tear down and rebuild a cluster? With dependsOn properly configured, Flux brings everything up in the right order automatically.

Multi-Tenancy and RBAC

Flux supports multi-tenancy through namespace isolation and ServiceAccount impersonation. Each team can own their namespace, with Flux reconciling their apps using a ServiceAccount that only has permissions within their namespace:

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: my-app
  namespace: team-a
spec:
  serviceAccountName: team-a-reconciler
  # ...

The team-a-reconciler ServiceAccount has RBAC permissions only in the team-a namespace. Team A can’t accidentally (or intentionally) touch Team B’s resources.

Drift Detection and Self-Healing

This is where Flux really earns its keep. Configure your HelmRelease with force: true or tune the reconciliation interval down, and Flux will continuously compare the desired state in your repo against the actual state in the cluster. Someone kubectl edited a deployment to change the replica count? Flux will revert it on the next reconciliation cycle.

This drives some operators crazy—”Flux keeps undoing my changes!”—which is actually the point. If you want a change to persist, put it in Git. The cluster is not a place to make persistent changes manually.

Flux vs. Traditional Deployment

Approach Audit Trail Rollback Drift Detection Learning Curve
kubectl apply Manual Manual None Low
Helm CLI Partial helm rollback None Low
Argo CD Git-based Git revert Yes Medium
Flux Git-based Git revert Yes Medium

Getting Started

Bootstrap Flux onto a cluster:

flux bootstrap github \
  --owner=my-org \
  --repository=my-fleet \
  --branch=main \
  --path=clusters/production \
  --personal

This creates the Flux controllers in your cluster and pushes the bootstrap configuration to your Git repo. From that point forward, the cluster manages itself based on what’s in the repo.

Closing Thoughts

Flux changes the way you think about Kubernetes operations. The reconciliation loop isn’t just a deployment mechanism—it’s a contract between your team and your infrastructure. What’s in Git is what runs. Everything else is noise.

Once you’ve run production workloads with Flux for a few months, going back to manual kubectl apply feels like going back to manually provisioning servers. Technically possible. Not something you’d choose.

Share this post: LinkedIn Reddit WhatsApp Mastodon
Jesse Borden

Jesse Borden

Software Engineer with an interest in hands on learning

I have several years of professional Information Technology (IT) experience leading staff and projects within the Department of War (DOW). I have managed Service Desk, Web Application Development, and System Administration teams. My two greatest passions are learning and conti...