Flux

Flux (Flux CD, the v2 generation) is a declarative, pull-based GitOps continuous-delivery toolkit for Kubernetes. Unlike a single application, Flux is a set of composable controllers — collectively the GitOps Toolkit — each owning a focused set of Custom Resources, communicating only through the Kubernetes API. Flux was accepted into the Cloud Native Computing Foundation (CNCF) and graduated in 2022, the same wave as its sibling ArgoCD (CNCF Flux page, TechTarget). Flux’s defining characteristics versus Argo CD are: a modular, CRD-first design with no built-in web UI; native, first-class image-update automation (it can watch a registry and write new image tags back to Git); and built-in SOPS decryption for encrypted secrets. Its philosophy is “Kubernetes-native building blocks” — small controllers you compose, each an instance of the Kubernetes Control Loop Pattern. This note covers the toolkit’s controllers, their CRDs, the reconcile flow, image automation, and SOPS. The general philosophy is in GitOps; the head-to-head is ArgoCD vs Flux.

Mental Model

Flux decomposes “deliver from Git” into orthogonal concerns, each handled by a dedicated controller and a dedicated CRD. The split is source acquisition (fetch artifacts) vs reconciliation (apply them) vs notification (events out, webhooks in) vs image automation (watch registry, write tags back). A Kustomization or HelmRelease does not fetch Git itself — it references a Source object that the source-controller keeps fresh. This indirection is the heart of Flux’s composability.

flowchart TB
    Git[(Git Repo / OCI Registry / Helm Repo / Bucket)]
    Registry[(Container Registry)]
    subgraph Toolkit[Flux GitOps Toolkit - controllers in flux-system namespace]
        SC[source-controller<br/>fetches + verifies artifacts]
        KC[kustomize-controller<br/>builds + applies Kustomize overlays]
        HC[helm-controller<br/>installs/upgrades Helm releases]
        NC[notification-controller<br/>events out + inbound webhooks]
        IRC[image-reflector-controller<br/>scans registry tags]
        IAC[image-automation-controller<br/>commits new tags to Git]
    end
    Git -->|clone/pull| SC
    SC -->|tarball artifact| KC
    SC -->|tarball / chart| HC
    KC -->|server-side apply| Cluster[(Kubernetes Cluster)]
    HC -->|Helm release| Cluster
    Registry -->|tag scan| IRC
    IRC -->|latest tag| IAC
    IAC -->|git commit + push| Git
    KC -->|reconcile events| NC
    HC -->|reconcile events| NC
    NC -->|Slack / Teams / GitHub status| Outside[External systems]
    NC -->|inbound webhook triggers| SC

What this diagram shows. Each controller is a single-responsibility reconciler. The source-controller is the only component that talks to Git/OCI/Helm-repo/Bucket sources; it produces an immutable, checksummed artifact that the apply controllers consume — so a manifest reconciler never re-clones. The kustomize- and helm-controllers are the two apply engines. The notification-controller is bidirectional: it pushes reconciliation events outward (Slack, GitHub commit status) and accepts inbound webhooks (a git push webhook triggers an immediate source refresh instead of waiting for the poll interval). The image-reflector + image-automation controllers form the image-update loop — scan a registry, pick the newest tag matching a policy, and commit that tag back into the Git repo — which then reconciles normally. The insight: Git is never bypassed, even by automation; the registry-watcher writes to Git, and the cluster only ever follows Git.

Mechanical Walk-through

You install Flux with flux bootstrap, which commits the toolkit’s own manifests into a Git repo and installs the controllers — Flux thereafter manages itself via GitOps.

A GitRepository source tells the source-controller: clone this repo, this branch/tag/semver-range, on this interval. The controller pulls, verifies (optional GPG signature / known-hosts), packages the checked-out tree into a .tar.gz artifact, stores it on its in-cluster HTTP file server, and updates the source’s status with the artifact URL and revision (commit SHA). Crucially the artifact is immutable and content-addressed.

A Kustomization CRD references that source by name and a path inside it. The kustomize-controller waits for the source’s artifact, fetches the tarball, runs an in-process Kustomize build (overlays, generators, transformers), and applies the result to the cluster via server-side apply (see Server-Side Apply). It tracks ownership of every applied object, and with prune: true it garbage-collects objects deleted from Git. It records the applied revision in status and reports a Ready condition. On the next interval it re-reconciles — applying again is idempotent, and if someone kubectl edits a managed object, the controller reverts it (drift correction).

A HelmRelease CRD is handled analogously by the helm-controller: it resolves a chart (from a HelmRepository, an OCIRepository, or a GitRepository source via an intermediate HelmChart), runs the equivalent of helm install / helm upgrade, and reconciles the release toward the declared values. It owns the release lifecycle including rollback-on-failed-upgrade.

The default reconcile interval is per-object (commonly 1m for sources, 10m for Kustomizations); a git push webhook routed to the notification-controller’s Receiver makes it near-immediate. flux reconcile ... from the CLI forces an out-of-band reconcile.

The Six Controllers and Their CRDs

ControllerResponsibilityCRDs owned
source-controllerFetch + verify artifacts from external sourcesGitRepository, OCIRepository, HelmRepository, HelmChart, Bucket
kustomize-controllerBuild Kustomize overlays, server-side apply, pruneKustomization
helm-controllerInstall / upgrade / rollback Helm releasesHelmRelease
notification-controllerDispatch events; receive inbound webhooksProvider, Alert, Receiver
image-reflector-controllerScan a registry’s tags, select latest per policyImageRepository, ImagePolicy
image-automation-controllerWrite the selected tag back into GitImageUpdateAutomation

Image-Update Automation

This is Flux’s signature feature with no built-in Argo CD equivalent. An ImageRepository tells the image-reflector-controller which registry repo to scan; an ImagePolicy declares the selection rule (semver range, numeric ordering, regex on tags) and exposes the chosen “latest” tag in its status. An ImageUpdateAutomation tells the image-automation-controller to take that selected tag, find the matching marker comments in the manifests inside a GitRepository, rewrite the image tag, and commit + push the change back to Git. The cluster then reconciles that commit through the normal source → kustomize path. The result: a newly-pushed image can roll out to the cluster with zero CI involvement and a full Git audit trail of every tag bump.

SOPS Decryption

Flux’s kustomize-controller has built-in SOPS (Secrets OPerationS) decryption. You commit encrypted Secret manifests to Git (encrypted with age or a cloud KMS key); the controller decrypts them at apply time using a key it holds in-cluster. This makes it safe to keep secrets in a Git repo — the central GitOps secret-management problem. See SOPS and Sealed Secrets for the alternatives.

Configuration / API Surface

# 1. A source: the source-controller keeps this Git repo's artifact fresh.
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: acme-manifests
  namespace: flux-system
spec:
  interval: 1m                        # how often to poll Git
  url: https://github.com/acme/manifests.git
  ref:
    branch: main                      # could be tag, semver, or commit
---
# 2. A Kustomization: the kustomize-controller applies a path from that source.
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: payments-api
  namespace: flux-system
spec:
  interval: 10m                       # reconcile cadence (applies even with no Git change)
  sourceRef:
    kind: GitRepository
    name: acme-manifests              # references the source above — no re-clone here
  path: ./apps/payments-api/overlays/prod
  prune: true                         # delete objects removed from Git
  targetNamespace: payments
  decryption:
    provider: sops                    # decrypt SOPS-encrypted Secrets at apply time
    secretRef: { name: sops-age }
  healthChecks:                       # block Ready until these are healthy
    - apiVersion: apps/v1
      kind: Deployment
      name: payments-api
      namespace: payments
---
# 3. Image automation: scan the registry, pick latest, commit the new tag to Git.
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: payments-api
  namespace: flux-system
spec:
  imageRepositoryRef: { name: payments-api }
  policy:
    semver: { range: ">=1.0.0" }      # newest tag satisfying this range wins

Line-by-line: the GitRepository and Kustomization are separate objects — the indirection via sourceRef is what lets many Kustomizations share one cloned source. interval exists on every Flux object — it is the reconcile heartbeat; reconciliation happens on the interval and on inbound webhook and on flux reconcile. prune: true is the drift/garbage-collection switch — without it, deleting a manifest from Git leaves the object running. decryption.provider: sops is the secrets story — encrypted Secrets in Git, decrypted only inside the cluster. healthChecks makes the Kustomization’s Ready condition (and thus dependency ordering via dependsOn) wait for real workload health, not just “applied.” The ImagePolicy semver range is the registry-watch selection rule that feeds the automation loop.

Failure Modes

  1. Source Ready=False — Git auth failure, bad branch ref, GPG verification mismatch, or network error. The kustomize-controller can’t apply because no artifact exists. Diagnose with flux get sources git / kubectl describe gitrepository.
  2. Kustomization stuck Reconciling — a healthCheck target never reaches healthy, or a dependsOn chain has a stalled upstream Kustomization. The whole dependency subtree blocks.
  3. Server-side apply field conflicts — another controller or a manual kubectl apply claimed ownership of a field Flux manages; SSA reports a conflict. Resolve via --force apply (spec.force: true) or by ceding the field.
  4. Image-automation commit storms — a too-loose ImagePolicy (e.g. matching every CI build tag) causes a Git commit on every push; the repo history fills with tag bumps. Tighten the policy to release tags.
  5. SOPS decryption failure — the in-cluster key doesn’t match the key the Secret was encrypted with, or the KMS key was rotated. The Secret applies as ciphertext or the Kustomization fails. The most common silent secrets bug.
  6. No UI to triage — Flux has no web UI; debugging is flux CLI + kubectl + reading CRD status conditions. Teams expecting an Argo-CD-style dashboard must install a third-party UI (Weave GitOps, Capacitor).

Alternatives and When to Choose Them

  • ArgoCD — the other CNCF-graduated GitOps tool. A (mostly) monolithic app with a strong web UI, an explicit Application abstraction, app-of-apps, project-based multi-tenancy, built-in SSO/RBAC. Choose Argo CD for a UI and a managed-app model; choose Flux for minimal composable controllers and native image automation. Full treatment: ArgoCD vs Flux.
  • CI pushing kubectl applypush-based delivery. Simpler initially, but no in-cluster reconciler, no drift correction, and CI holds cluster credentials. Flux exists to remove that — see GitOps.
  • Weave GitOps / Capacitor — third-party web UIs for Flux, not alternatives to it; they fill the “Flux has no UI” gap. Capacitor is the CNCF-aligned successor after Weaveworks (Flux’s original sponsor) wound down.
  • Helm / Kustomize alone — renderers, not deliverers. Flux’s helm-controller and kustomize-controller wrap them with reconciliation. Not competitors.

Production Notes

  • The modular controller design means you can run only what you need — a cluster doing no Helm need not run helm-controller, a cluster doing no image automation need not run the two image controllers. This minimal-footprint composability is Flux’s central design pitch versus Argo CD’s heavier monolith.
  • flux bootstrap installs Flux such that Flux manages its own manifests from Git — the toolkit is self-reconciling from day one, and upgrading Flux is a Git commit.
  • Native image automation is the feature most often cited as the reason to pick Flux: a fully Git-mediated path from “new image pushed” to “rolled out,” with no separate CI step and a complete audit trail in commit history.
  • The simultaneous 2022 CNCF graduation of Flux and Argo CD (TechTarget) signalled GitOps becoming a default production delivery model rather than an emerging practice.
  • Operational reality: because Flux is CRD-first with no UI, teams adopting it standardize on the flux CLI and on alerting (notification-controller Alert/Provider) for visibility — the observability is event-driven, not dashboard-driven.

See Also