Flux CD¶
Who this page is for: in plain English, Flux is a set of controllers that watch a Git repository and continuously make your cluster match what's committed there. This page is production depth, aimed at readers already comfortable with Pods, Services, and Deployments. New to Kubernetes? Start with the Beginner track.
Flux is a CNCF graduated GitOps toolkit for Kubernetes. Like Argo CD, it continuously reconciles cluster state against what's declared in Git - but where Argo CD is a single application with a UI-centric mental model, Flux is a set of small, composable controllers, each owning one concern and one family of CRDs. There's no bundled UI; Flux is designed to be operated via kubectl, the flux CLI, and GitOps itself.
The two projects solve the same core problem - pull-based, Git-as-source-of-truth delivery - with different architectural bets. Flux's bet is composability: you can run just the source-controller and kustomize-controller if that's all you need, or add helm-controller and image-automation-controller as requirements grow. That granularity is Flux's defining trait, and it's also why the comparison to Argo CD comes up in nearly every GitOps conversation.
The GitOps Toolkit architecture¶
flowchart TD
Git[(Git repository)] --> SC[source-controller]
OCI[(OCI registry)] --> SC
HelmRepo[(Helm repository)] --> SC
SC --> Artifact[(fetched artifact\ncached + versioned)]
Artifact --> KC[kustomize-controller]
Artifact --> HC[helm-controller]
KC --> K8s[Kubernetes API]
HC --> HelmSDK[Helm SDK\nreal helm release objects]
HelmSDK --> K8s
K8s -.drift.-> KC
K8s -.drift.-> HC
Registry[(container registry)] --> IRC[image-reflector-controller]
IRC --> IAC[image-automation-controller]
IAC -->|commits new tags| Git
KC --> NC[notification-controller]
HC --> NC
NC --> Slack[Slack / webhook / PagerDuty]
Each controller is independently deployable and owns its own CRDs:
| Controller | CRDs it reconciles | Job |
|---|---|---|
source-controller |
GitRepository, OCIRepository, HelmRepository, HelmChart, Bucket |
Fetches artifacts from Git, OCI registries, Helm repos, or S3-compatible buckets; exposes them as a versioned, content-addressed artifact other controllers consume |
kustomize-controller |
Kustomization |
Builds and applies a Kustomize overlay (or plain YAML) from a Source, detects and corrects drift |
helm-controller |
HelmRelease |
Drives real Helm release installs/upgrades from a HelmChart produced by source-controller |
notification-controller |
Provider, Alert, Receiver |
Sends events out to Slack/webhooks/PagerDuty, and accepts inbound webhooks to trigger reconciliation |
image-reflector-controller |
ImageRepository, ImagePolicy |
Scans container registries and evaluates tag policies (semver, alphabetical, filtered) |
image-automation-controller |
ImageUpdateAutomation |
Commits updated image tags back to Git when a policy match is found |
Flux vs. Argo CD¶
| Flux | Argo CD | |
|---|---|---|
| Architecture | multiple small controllers, each with own CRDs | single application-controller, monolithic |
| UI | none bundled (Weave GitOps / third-party UIs exist) | built-in web UI |
| Reconciliation unit | Kustomization (or HelmRelease) |
Application |
| Helm handling | drives real helm release objects via helm-controller |
renders charts by invoking helm template, then applies the output as plain manifests - no release Secret, no helm history/helm rollback, no chart-native hook lifecycle |
| Multi-source composition | dependsOn between Kustomizations/HelmReleases |
sync waves, App of Apps |
| Image update automation | native (image-reflector-controller + image-automation-controller), commits back to Git |
not native - requires the separate Argo CD Image Updater project |
| Progressive delivery | delegates to Flagger | delegates to Argo Rollouts |
| Multi-tenancy model | namespace-scoped Kustomization/HelmRelease + RBAC impersonation via serviceAccountName |
AppProject-based |
| CLI | flux |
argocd |
| Bootstrap | flux bootstrap writes its own manifests into your Git repo |
installed via manifest/Helm, Applications added separately |
The Helm distinction is the one that surprises people most: Argo CD's "Helm support" shells out to helm template and then diffs/applies the resulting YAML - there's no helm.sh/v1 release Secret, no helm rollback, no helm history. Flux's HelmRelease drives the actual Helm SDK, creating real Helm releases indistinguishable from ones you'd install by hand with helm install. If you depend on Helm hooks, helm test, or release history/rollback semantics, Flux's model matches Helm's own behavior more closely.
Source CRDs¶
Everything in Flux starts with a Source - a versioned, content-addressed artifact that other controllers consume as input.
GitRepository - the most common source:
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: platform-config
namespace: flux-system
spec:
interval: 1m
url: https://github.com/myorg/k8s-config
ref:
branch: main
ignore: |
/*
!/apps
!/infrastructure
OCIRepository - for teams that push rendered manifests or Kustomize overlays as OCI artifacts instead of tracking raw Git:
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: platform-config-oci
namespace: flux-system
spec:
interval: 5m
url: oci://ghcr.io/myorg/k8s-config
ref:
tag: latest
HelmRepository - points at a classic Helm chart repo or an OCI Helm registry, feeding HelmChart objects that helm-controller consumes:
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: bitnami
namespace: flux-system
spec:
interval: 1h
url: https://charts.bitnami.com/bitnami
Sources are polled on interval, but reconciliation can also be triggered instantly via a webhook receiver (Receiver CRD) wired to your Git host, avoiding the poll-interval lag entirely.
Kustomization: the reconciliation unit¶
The Kustomization CRD is Flux's core deployment primitive - and yes, the name collision with the Kustomize tool's own kustomization.yaml is deliberate and a little confusing. A Flux Kustomization points at a directory containing a Kustomize overlay (or even just plain YAML with no kustomization.yaml at all) and reconciles it; the underlying build step is the same kustomize build that kubectl apply -k runs.
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: api-service
namespace: flux-system
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: platform-config
path: ./apps/api-service/overlays/production
prune: true
wait: true
timeout: 3m
targetNamespace: production
dependsOn:
- name: infrastructure
patches:
- patch: |
- op: replace
path: /spec/replicas
value: 5
target:
kind: Deployment
name: api-service
prune: true removes resources deleted from Git - the Flux equivalent of Argo CD's syncPolicy.automated.prune. wait: true blocks the Kustomization from being marked Ready until all applied resources report healthy, which matters for dependsOn ordering. patches lets a Flux Kustomization apply additional JSON6902/strategic-merge patches on top of what the referenced overlay already produces, without touching the Git-tracked overlay itself.
dependsOn: ordering across Kustomizations¶
dependsOn expresses a hard dependency: the dependent Kustomization won't even attempt to apply until the one it depends on reports Ready. This is Flux's answer to Argo CD's sync waves, but expressed as an explicit graph between reconciliation units rather than a numeric annotation within one:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: api-service
namespace: flux-system
spec:
dependsOn:
- name: crds
- name: cert-manager
- name: infrastructure
Typical chain: crds -> infrastructure (namespaces, RBAC, cert-manager, ingress controller) -> per-app Kustomizations. Each stage's Kustomization must reach Ready: True (all resources applied and healthy, if wait: true) before dependents are attempted.
HelmRelease: declarative real-Helm management¶
HelmRelease is Flux's most-cited differentiator. It doesn't render a chart itself - it references a HelmChart (produced from a HelmRepository, GitRepository, or OCIRepository source) and drives the actual Helm SDK to install/upgrade/rollback a real release.
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: redis
namespace: production
spec:
interval: 10m
chart:
spec:
chart: redis
version: "19.x"
sourceRef:
kind: HelmRepository
name: bitnami
namespace: flux-system
values:
architecture: replication
auth:
enabled: true
existingSecret: redis-auth
install:
remediation:
retries: 3
upgrade:
remediation:
remediateLastFailure: true
dependsOn:
- name: infrastructure
namespace: flux-system
install.remediation.retries and upgrade.remediation.remediateLastFailure give you automatic rollback-and-retry behavior on a failed release - Flux calls helm rollback under the hood when remediation kicks in, using Helm's own release history. values can be inlined as above, or composed from multiple valuesFrom ConfigMap/Secret references for layering environment-specific overrides - the Flux equivalent of chaining -f values.yaml -f values-prod.yaml.
Pull-based reconciliation and drift correction¶
Every controller in Flux runs the same loop: fetch/observe desired state, compare to live cluster state, converge. source-controller polls on interval; kustomize-controller and helm-controller also reconcile on that same cadence even if nothing changed in Git, specifically to catch drift - a manual kubectl edit or kubectl delete against a Flux-managed resource gets reverted on the next reconciliation pass, the same self-healing guarantee Argo CD provides via selfHeal: true. Unlike Argo CD, this drift correction is Flux's default behavior, not an opt-in flag.
Force an immediate reconciliation without waiting for the interval:
flux reconcile source git platform-config
flux reconcile kustomization api-service --with-source
flux reconcile helmrelease redis -n production
Image update automation¶
This is the other capability Argo CD doesn't provide natively (it requires the separate Argo CD Image Updater project). Flux can watch a registry, evaluate a tag against a policy, and commit the new tag back to Git itself - keeping Git as the literal source of truth even for automated image bumps, rather than mutating the cluster out-of-band.
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageRepository
metadata:
name: api-service
namespace: flux-system
spec:
image: ghcr.io/myorg/api-service
interval: 5m
---
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImagePolicy
metadata:
name: api-service
namespace: flux-system
spec:
imageRepositoryRef:
name: api-service
policy:
semver:
range: ">=1.0.0 <2.0.0"
---
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageUpdateAutomation
metadata:
name: api-service
namespace: flux-system
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: platform-config
git:
commit:
author:
email: flux@example.com
name: fluxcdbot
messageTemplate: |
Automated image update
{{range .Updated.Images}}{{println .}}{{end}}
update:
path: ./apps/api-service
strategy: Setters
The Setters strategy relies on inline marker comments in your manifests (# {"$imagepolicy": "flux-system:api-service"}) telling image-automation-controller exactly which image: field to rewrite. The controller clones the repo, bumps the tag, and pushes a commit - so a CI pipeline that pushes a new image with a matching semver tag results in an automatic, auditable Git commit updating the deployed version, no PR-opening bot required (though ImageUpdateAutomation can also target a separate push branch if you want a PR gate instead of a direct commit).
Multi-tenancy and RBAC¶
Flux supports per-tenant impersonation: a Kustomization or HelmRelease can specify serviceAccountName, and the controller impersonates that ServiceAccount (via Kubernetes impersonation, not its own elevated permissions) when applying resources. This lets a platform team run one set of Flux controllers cluster-wide while scoping each tenant's Kustomization to only the RBAC permissions its own ServiceAccount holds:
Combined with namespace-scoped Kustomization/HelmRelease objects and Kubernetes-native RBAC (see RBAC), this is Flux's multi-tenancy story - no separate AppProject abstraction, just Kubernetes primitives.
Notifications¶
notification-controller sends reconciliation events (success, failure, health changes) to external systems, and can also receive inbound webhooks to trigger immediate reconciliation instead of waiting on interval.
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
name: slack
namespace: flux-system
spec:
type: slack
channel: platform-alerts
secretRef:
name: slack-webhook-url
---
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
name: api-service-alerts
namespace: flux-system
spec:
providerRef:
name: slack
eventSeverity: error
eventSources:
- kind: Kustomization
name: api-service
- kind: HelmRelease
name: redis
flux bootstrap¶
flux bootstrap is both the installation mechanism and a self-referential first commit: it installs the controllers into the cluster and writes their own manifests, plus an initial GitRepository/Kustomization pointing back at that same repo, into Git - so from the first command, Flux is managing its own installation via GitOps.
flux bootstrap github \
--owner=myorg \
--repository=k8s-config \
--branch=main \
--path=clusters/production \
--personal
Re-running flux bootstrap against an already-bootstrapped cluster is idempotent - it's the standard way to upgrade Flux's own controllers or repair a cluster that's drifted from its bootstrap manifests.
For fleets of clusters, Flux Operator - a separate project from the Flux maintainers' commercial arm - has emerged as an alternative to flux bootstrap: a FluxInstance CRD declaratively manages the controllers' lifecycle (installation, upgrades, CVE patching) instead of a one-shot CLI command, and it's increasingly the recommended path for production fleets rather than a hand-run flux bootstrap per cluster. It's additive, not a replacement - flux bootstrap remains the standard single-cluster onboarding path.
One migration note if you're running an older Flux install: Flux 2.8 (February 2026) removed the long-deprecated source.toolkit.fluxcd.io/v1beta2, kustomize.toolkit.fluxcd.io/v1beta2, and helm.toolkit.fluxcd.io/v2beta2 API versions outright. Run flux migrate before upgrading a cluster still using those to rewrite manifests onto the current stable versions shown throughout this page.
Common mistakes¶
- Confusing the
KustomizationCRD with akustomization.yamlfile. They're related but distinct - a FluxKustomizationpoints at a directory and can reconcile even a directory with nokustomization.yamlat all (plain YAML). - No
dependsOnbetween infrastructure and workloads. CRDs, namespaces, and cert-manager need to exist before dependent Kustomizations reconcile - withoutdependsOn, Flux applies everything based on interval timing, which is a race. - Setting
intervaltoo aggressively low onGitRepository. Polling every few seconds against a hosted Git provider adds load for no real benefit - use webhook receivers for low-latency triggering instead of shrinking the poll interval. - Forgetting
prune: true. Same failure mode as Argo CD withoutprune- resources removed from Git are silently orphaned in the cluster. - Not using
HelmReleaseremediation settings. A failed Helm upgrade with noremediateLastFailurecan leave a release stuck infailedstate requiring manualhelm rollback. - Treating
flux bootstrapas a one-time setup step. It's meant to be re-run for upgrades and disaster recovery - don't hand-edit the bootstrap-generated manifests and expect them to survive a re-bootstrap. - Running one giant
Kustomizationfor the whole cluster. Split by concern (infrastructure vs. apps, per-team) so a bad change and itspruneblast radius stay contained.
Source Links¶
- Flux documentation
- fluxcd/flux2 on GitHub and its releases
- Flux GitRepository / source-controller API
- Flux Kustomization API
- Flux HelmRelease API
- Flux image update automation
- Flux multi-tenancy guide
- CNCF project page: Flux
- Flagger (progressive delivery)
- Argo CD Helm documentation (for the
helm templatebehaviour compared above)
Related Concepts¶
- Argo CD - the other major GitOps engine, monolithic vs. Flux's composable controllers
- Kustomize - the overlay tool that Flux's
KustomizationCRD builds under the hood - Helm - the packaging format
HelmReleasedrives as real releases - cert-manager - a typical
infrastructureKustomization dependency bootstrapped before app-layer resources - Operators and CRDs - the controller-reconciliation pattern every Flux controller follows
- RBAC - backs Flux's
serviceAccountNameimpersonation model for multi-tenancy - Troubleshooting - debugging a Kustomization stuck in
Reconciling