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.
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
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.
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.
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.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.