Skip to content

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:

spec:
  serviceAccountName: team-payments-deployer

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 Kustomization CRD with a kustomization.yaml file. They're related but distinct - a Flux Kustomization points at a directory and can reconcile even a directory with no kustomization.yaml at all (plain YAML).
  • No dependsOn between infrastructure and workloads. CRDs, namespaces, and cert-manager need to exist before dependent Kustomizations reconcile - without dependsOn, Flux applies everything based on interval timing, which is a race.
  • Setting interval too aggressively low on GitRepository. 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 without prune - resources removed from Git are silently orphaned in the cluster.
  • Not using HelmRelease remediation settings. A failed Helm upgrade with no remediateLastFailure can leave a release stuck in failed state requiring manual helm rollback.
  • Treating flux bootstrap as 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 Kustomization for the whole cluster. Split by concern (infrastructure vs. apps, per-team) so a bad change and its prune blast radius stay contained.
  • Argo CD - the other major GitOps engine, monolithic vs. Flux's composable controllers
  • Kustomize - the overlay tool that Flux's Kustomization CRD builds under the hood
  • Helm - the packaging format HelmRelease drives as real releases
  • cert-manager - a typical infrastructure Kustomization dependency bootstrapped before app-layer resources
  • Operators and CRDs - the controller-reconciliation pattern every Flux controller follows
  • RBAC - backs Flux's serviceAccountName impersonation model for multi-tenancy
  • Troubleshooting - debugging a Kustomization stuck in Reconciling