DevOps · Kubernetes

Flux CD for GitOps — A Field Guide

How Flux v2's separate controllers — source, kustomize, helm, notification — reconcile a cluster from Git, and how that composable architecture compares to Argo CD's single Application CRD and built-in UI.

John Kihiu12 min read

How Flux v2's separate controllers — source, kustomize, helm, notification — reconcile a cluster from Git, and how that composable architecture compares to Argo CD's single Application CRD and built-in UI. I've run both in production, and the choice mostly comes down to whether you want a UI dashboard by default or a set of small controllers you wire together yourself.

Flux v2 is four controllers, not one

The thing that surprises people coming from Argo CD is that "Flux" isn't a single binary or a single CRD — it's a set of independent controllers that each reconcile one concern. The source-controller watches Git repositories, Helm repositories, and OCI registries and produces artifacts other controllers consume. The kustomize-controller applies Kustomize overlays from those artifacts. The helm-controller manages Helm releases. The notification-controller handles alerting and webhook receivers. Each one is a separate reconciler with its own CRDs, its own logs, and its own failure domain — a source-controller outage stalls new artifact fetches but doesn't touch already-applied resources, which is a very different blast radius than a single monolithic sync loop.

Source CRDs vs. Kustomization and HelmRelease

Flux splits "where does the config come from" from "what do I do with it." GitRepository, HelmRepository, and OCIRepository are Source CRDs — they just describe where to pull from and how often, and the source-controller produces a versioned artifact when something changes. Kustomization and HelmRelease are the objects that actually drive reconciliation: a Kustomization points at a GitRepository source and applies a path from it with kustomize-controller; a HelmRelease points at a HelmRepository or OCIRepository and installs/upgrades a chart with helm-controller. This separation is why you can point five different Kustomizations at the same GitRepository without five separate git clones — the source is fetched once and reused.

YAML · gitrepository-and-kustomization.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: platform-config
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/acme/platform-config
  ref:
    branch: main
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: payments-namespace
  namespace: flux-system
spec:
  interval: 5m
  path: "./clusters/prod/payments"
  prune: true
  sourceRef:
    kind: GitRepository
    name: platform-config
  targetNamespace: payments
prune: true is doing real work

With pruning on, deleting a manifest from the Git path removes the matching resource from the cluster on the next reconcile. That's the GitOps guarantee working correctly, but it means a manifest moved or renamed in Git without care can delete a live resource — treat renames as delete-then-create, not a no-op.

HelmRelease drives chart installs the same way

A HelmRelease works the same shape as a Kustomization but for Helm charts: it references a HelmRepository or OCIRepository as its source, names a chart and version, and carries a values block that overrides the chart's defaults. helm-controller reconciles it exactly like `helm upgrade --install` would, but continuously — drift in the cluster (someone `kubectl edit`-ing a Deployment the chart owns) gets reverted on the next reconciliation instead of silently persisting.

YAML · helmrelease.yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: ingress-nginx
  namespace: flux-system
spec:
  interval: 10m
  chart:
    spec:
      chart: ingress-nginx
      version: "4.11.x"
      sourceRef:
        kind: HelmRepository
        name: ingress-nginx
  values:
    controller:
      replicaCount: 2
      service:
        type: LoadBalancer

Flux vs. Argo CD, architecturally

Argo CD centers on a single Application CRD: one object describes a source, a destination, and sync policy, and the whole thing is application-centric — you think in terms of "this app, deployed here, from this repo." It ships a UI by default, which is a big part of its appeal for teams that want a visual diff of what's out of sync before they approve a sync. Flux takes the Unix-philosophy route: separate composable controllers, each doing one reconciliation job, wired together through CRDs that reference each other, with no UI shipped in the core product — you get visibility through `flux get all`, `flux logs`, Prometheus metrics the controllers expose, or a separate project like Weave GitOps if you want a dashboard. Neither is "more GitOps" than the other; the real trade-off is whether you want an opinionated application-centric tool with a UI included, or a toolkit of composable reconcilers you assemble yourself.

No UI by default is a real operational cost

Without Weave GitOps or a similar add-on, diagnosing "why hasn't this deployed" in Flux means reading `flux get kustomizations`, `flux get helmreleases`, and controller logs across four separate components. That's fine once your team knows the CLI, but it's a slower onboarding curve than Argo CD's UI for anyone new to the cluster.

Wrapping up

Flux v2's real shape is four independent controllers — source, kustomize, helm, notification — reconciling Source CRDs into Kustomizations and HelmReleases, rather than one tool doing everything. That buys composability and a smaller blast radius per failure, at the cost of no built-in UI and a CLI-first debugging workflow. Argo CD trades that composability for a single Application CRD and a dashboard out of the box. Pick Flux if you want controllers you can reason about independently and don't mind living in `flux get`; pick Argo CD if a visual sync-diff for your team matters more than architectural purity.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.