Back to Modern DevOps & Platform Engineering Series

Flux CD — Complete Tool Reference Guide

May 15, 2026 Wasil Zafar 28 min read

A comprehensive reference to Flux CD — the GitOps Toolkit from Weaveworks, now a CNCF graduated project, built as a set of composable Kubernetes controllers for source-driven continuous delivery.

Table of Contents

  1. Overview & History
  2. GitOps Toolkit Architecture
  3. Installation & Bootstrap
  4. Source Controller
  5. Kustomize Controller
  6. Helm Controller
  7. Image Automation
  8. Multi-Tenancy
  9. Notification Controller
  10. Flux vs Argo CD
  11. Troubleshooting

Overview & History

Flux CD is a GitOps continuous-delivery system for Kubernetes built as a collection of single-purpose controllers — collectively the GitOps Toolkit. Created by Weaveworks (the same team that coined the term "GitOps" in 2017), Flux v1 launched as a single monolithic controller. The team rewrote it from scratch as Flux v2 in 2020 to embrace the controller-per-concern pattern that mirrors Kubernetes' own architecture.

Flux became a CNCF incubating project in 2019 and graduated to the top tier in 2022 — joining Kubernetes, Helm, and Argo as fully-vetted CNCF projects. It is the reference implementation referenced in CNCF's GitOps Working Group standards and powers production GitOps at companies including Mercedes-Benz, SAP, RingCentral, and Bloomberg.

Key Insight: Where Argo CD presents GitOps as a single product with a UI front-and-centre, Flux presents it as a toolkit — a set of Lego bricks that platform teams compose to fit their workflow. There is no Flux UI in the box (third-party UIs like Weave GitOps and Capacitor exist), and that is by design: Flux assumes you already have a developer portal, dashboard, or CLI as your interface.

GitOps Toolkit Architecture

Flux v2 ships as four core controllers plus optional add-ons. Each runs as a Kubernetes Deployment in the flux-system namespace, watches its own Custom Resources, and reconciles them independently.

Flux GitOps Toolkit Components
flowchart TD
    Git["Git Repository"] --> Source["source-controller
GitRepository / OCIRepository / HelmRepository"] Source --> Kustomize["kustomize-controller
Reconciles Kustomization CR"] Source --> Helm["helm-controller
Reconciles HelmRelease CR"] Kustomize --> Cluster["Kubernetes Cluster
(Workloads)"] Helm --> Cluster Image["image-reflector +
image-automation-controller"] --> Git Cluster -.events.-> Notify["notification-controller
Slack · GitHub · Webhook"] style Git fill:#e8f4f4,stroke:#3B9797,color:#132440 style Source fill:#f0f4f8,stroke:#16476A,color:#132440 style Kustomize fill:#f0f4f8,stroke:#16476A,color:#132440 style Helm fill:#f0f4f8,stroke:#16476A,color:#132440 style Image fill:#f0f4f8,stroke:#16476A,color:#132440 style Notify fill:#fff5f5,stroke:#BF092F,color:#132440 style Cluster fill:#132440,stroke:#132440,color:#ffffff
ControllerWatchesResponsibility
source-controllerGitRepository, OCIRepository, HelmRepository, BucketPulls source artefacts and exposes them as tarballs over HTTP for other controllers
kustomize-controllerKustomizationBuilds Kustomize overlays and applies them to the cluster
helm-controllerHelmReleaseInstalls/upgrades Helm charts using HelmRepository or OCI sources
notification-controllerProvider, Alert, ReceiverSends events to external systems and accepts webhooks for instant reconciliation
image-reflectorImageRepository, ImagePolicyScans container registries for new tags matching policies
image-automationImageUpdateAutomationCommits image tag updates back to Git

Installation & Bootstrap

Flux is installed by bootstrapping — the flux bootstrap command installs the controllers, then commits the controller manifests themselves into your Git repository so Flux manages itself going forward.

# Install the Flux CLI (macOS / Linux)
curl -s https://fluxcd.io/install.sh | sudo bash

# Verify cluster prerequisites (Kubernetes 1.28+, network access to Git)
flux check --pre

# Bootstrap against a GitHub repository
# This will:
#   1. Create the repository if it doesn't exist
#   2. Install the Flux controllers in the cluster
#   3. Commit the controller manifests to ./clusters/production/flux-system/
#   4. Configure Flux to reconcile that directory
export GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx

flux bootstrap github \
  --owner=my-org \
  --repository=fleet-infra \
  --branch=main \
  --path=./clusters/production \
  --personal=false \
  --components-extra=image-reflector-controller,image-automation-controller

# After bootstrap, verify
flux get all
kubectl -n flux-system get pods

Source Controller

The source controller is the foundation: every other Flux controller reads its desired state from a Source Custom Resource. Sources can be Git repositories, OCI artefacts (signed container images containing manifests), Helm repositories, or S3-compatible buckets.

# gitrepository-app-config.yaml
# A GitRepository source — Flux will poll this every minute and expose
# the latest commit's contents as an HTTP-fetchable tarball
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: payments-config
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/my-org/payments-config
  ref:
    branch: main
  secretRef:
    name: github-deploy-key   # SSH key or HTTPS token
  ignore: |
    # Patterns to exclude from the snapshot
    /docs/
    /scripts/
    *.md
---
# OCIRepository — pull manifests from a signed OCI artefact
# Useful for the "Git-free" GitOps pattern where CI pushes signed bundles
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
  name: payments-bundle
  namespace: flux-system
spec:
  interval: 5m
  url: oci://ghcr.io/my-org/payments-bundle
  ref:
    semver: ">=1.0.0 <2.0.0"
  verify:
    provider: cosign
    secretRef:
      name: cosign-pub-key

Kustomize Controller

The kustomize-controller takes a Source plus a path inside it, runs kustomize build, and applies the result. It also performs health checks, drift detection, and pruning of resources removed from Git.

# kustomization-payments.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: payments-app
  namespace: flux-system
spec:
  interval: 5m
  path: ./apps/payments/overlays/production
  prune: true                  # Delete resources removed from Git
  sourceRef:
    kind: GitRepository
    name: payments-config
  targetNamespace: payments
  wait: true                   # Block reconciliation until health checks pass
  timeout: 5m
  retryInterval: 2m
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: payments-api
      namespace: payments
    - apiVersion: apps/v1
      kind: Deployment
      name: payments-worker
      namespace: payments
  postBuild:
    substitute:
      cluster_name: prod-eu
      region: eu-west-1
    substituteFrom:
      - kind: ConfigMap
        name: cluster-vars
      - kind: Secret
        name: cluster-secrets
        optional: true
  decryption:
    provider: sops              # SOPS-encrypted secrets supported natively
    secretRef:
      name: sops-age-key
Pattern Dependency Ordering
Wait-And-Order Without an Orchestrator

One of Flux's most underrated features is dependency ordering between Kustomizations. By setting spec.dependsOn, you can guarantee the database operator is healthy before the application that consumes it reconciles, and that cert-manager is ready before any Ingress that requests certificates. Combined with wait: true, this gives you "wave-based" deployments without Argo CD's sync-waves annotation system. Mercedes-Benz uses this pattern across 900+ Kustomizations to bootstrap entire fleet clusters from a single Git push.

Dependencies Ordering Bootstrap

Helm Controller

HelmReleases are the Flux equivalent of installing a chart. Unlike running helm install manually, the controller continuously reconciles the desired release state and supports remediation strategies on failure.

# helmrelease-ingress-nginx.yaml
# First, declare the chart repository as a HelmRepository source
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
  name: ingress-nginx
  namespace: flux-system
spec:
  interval: 1h
  url: https://kubernetes.github.io/ingress-nginx
---
# Then declare the release
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
spec:
  interval: 10m
  chart:
    spec:
      chart: ingress-nginx
      version: "4.x"
      sourceRef:
        kind: HelmRepository
        name: ingress-nginx
        namespace: flux-system
      interval: 1h
  install:
    createNamespace: true
    remediation:
      retries: 3
  upgrade:
    cleanupOnFail: true
    remediation:
      retries: 3
      remediateLastFailure: true
      strategy: rollback
  values:
    controller:
      replicaCount: 3
      service:
        type: LoadBalancer
        annotations:
          service.beta.kubernetes.io/aws-load-balancer-type: nlb
      metrics:
        enabled: true
        serviceMonitor:
          enabled: true

Image Automation

One of Flux's distinctive features (no native equivalent in Argo CD) is automatic image update PRs/commits. The image-reflector watches container registries for new tags matching a policy, and image-automation commits the new tag back to Git — closing the loop from CI build to production deploy without human intervention.

# image-automation-payments.yaml
# Watch the registry for new image tags
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
  name: payments-api
  namespace: flux-system
spec:
  image: ghcr.io/my-org/payments-api
  interval: 1m
  secretRef:
    name: ghcr-pull-secret
---
# Define which tags are eligible (semver, regex, or numerical)
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 <2.0.0"   # Auto-promote patch and minor, never major
---
# Commit the chosen tag back to Git
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageUpdateAutomation
metadata:
  name: payments-config
  namespace: flux-system
spec:
  interval: 1m
  sourceRef:
    kind: GitRepository
    name: payments-config
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        email: fluxcdbot@my-org.com
        name: fluxcdbot
      messageTemplate: |
        Update images
        {{ range .Updated.Images }}
        - {{ .Name }}: {{ .NewTag }}
        {{ end }}
    push:
      branch: main
  update:
    path: ./apps/payments
    strategy: Setters

The "Setters" strategy uses inline markers in your YAML files to indicate which values to update:

# apps/payments/deployment.yaml — note the marker comment
spec:
  template:
    spec:
      containers:
        - name: api
          image: ghcr.io/my-org/payments-api:1.2.3 # {"$imagepolicy": "flux-system:payments-api"}

Multi-Tenancy

Flux supports true multi-tenancy where tenant Kustomizations and HelmReleases run with restricted ServiceAccounts that cannot escalate beyond their tenant namespace.

# tenant-payments-flux.yaml
# Tenant namespace + ServiceAccount + RBAC
apiVersion: v1
kind: Namespace
metadata:
  name: tenant-payments
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: payments-flux
  namespace: tenant-payments
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: payments-flux-admin
  namespace: tenant-payments
subjects:
  - kind: ServiceAccount
    name: payments-flux
    namespace: tenant-payments
roleRef:
  kind: ClusterRole
  name: admin           # Namespace-scoped admin only
  apiGroup: rbac.authorization.k8s.io
---
# Tenant's GitRepository and Kustomization use the impersonated SA
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: payments-app
  namespace: tenant-payments
spec:
  interval: 1m
  url: https://github.com/payments-team/payments-app
  ref:
    branch: main
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: payments-app
  namespace: tenant-payments
spec:
  serviceAccountName: payments-flux  # Critical: enforces tenant boundary
  interval: 5m
  path: ./deploy
  prune: true
  sourceRef:
    kind: GitRepository
    name: payments-app
  targetNamespace: tenant-payments
Security Critical: Without serviceAccountName, a tenant Kustomization runs as the cluster-admin kustomize-controller ServiceAccount and can apply any resource cluster-wide. Always set this field for tenant-managed resources, and pair it with a Kyverno or OPA policy that requires all Kustomizations outside flux-system to specify a ServiceAccount.

Notification Controller

The notification-controller is bidirectional: it sends Flux events to external systems (Slack, GitHub, Microsoft Teams, generic webhooks, OpsGenie, PagerDuty) and accepts inbound webhooks (from GitHub push, Harbor image push, etc.) to trigger immediate reconciliation rather than waiting for the next polling interval.

# slack-alert.yaml
# Outbound: send Flux events to Slack
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Provider
metadata:
  name: slack-platform
  namespace: flux-system
spec:
  type: slack
  channel: "#flux-events"
  secretRef:
    name: slack-webhook
---
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
  name: production-alerts
  namespace: flux-system
spec:
  providerRef:
    name: slack-platform
  eventSeverity: error          # info | error
  eventSources:
    - kind: Kustomization
      name: '*'
    - kind: HelmRelease
      name: '*'
  exclusionList:
    - "waiting for next reconciliation"  # Reduce noise
---
# Inbound: webhook receiver for instant reconciliation on git push
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Receiver
metadata:
  name: github-receiver
  namespace: flux-system
spec:
  type: github
  events:
    - "ping"
    - "push"
  secretRef:
    name: github-webhook-token
  resources:
    - apiVersion: source.toolkit.fluxcd.io/v1
      kind: GitRepository
      name: payments-config

Flux vs Argo CD

Both projects implement GitOps for Kubernetes, both are CNCF graduated, and both are excellent. They differ in philosophy more than capability.

DimensionFlux CDArgo CD
ArchitectureMultiple specialised controllers (toolkit)Monolithic application server + UI
UINone built-in (third-party: Weave GitOps, Capacitor)Rich web UI included
App definitionKustomization + HelmRelease CRDsApplication + ApplicationSet CRDs
Image automationFirst-class, commits back to GitAdd-on (Argo Image Updater), commits or annotations
Multi-clusterHub-and-spoke or pull-per-clusterHub-and-spoke (Argo CD on management cluster)
SecretsNative SOPS, Sealed Secrets, External Secrets via add-onExternal Secrets, Vault plugin, Sealed Secrets
Mental modelCompose Lego bricksUse the product
Best fitPlatform teams who want flexibility, dev portals as UITeams who want a turnkey experience with great UI

Troubleshooting

# See all Flux objects in the cluster
flux get all -A

# Inspect a specific Kustomization (status, last applied, errors)
flux get kustomization payments-app -n flux-system
flux describe kustomization payments-app -n flux-system

# Force immediate reconciliation
flux reconcile kustomization payments-app --with-source

# Suspend reconciliation (e.g. during incident)
flux suspend kustomization payments-app
# ...investigate and fix...
flux resume kustomization payments-app

# Tail controller logs
flux logs --kind=Kustomization --name=payments-app -f

# Check why a HelmRelease is failing
flux get helmrelease ingress-nginx -n ingress-nginx
kubectl describe helmrelease ingress-nginx -n ingress-nginx

# Diff what would change without applying
flux diff kustomization payments-app --path ./apps/payments/overlays/production

# Export the current Flux installation as YAML (for backup or migration)
flux export source git --all > sources.yaml
flux export kustomization --all > kustomizations.yaml

Common pitfalls:

  • Stuck reconciliation: Usually a missing CRD or a webhook timing out. Check kubectl get events -n flux-system.
  • Image automation not committing: Verify the deploy key has write access and the marker comment exactly matches the policy reference.
  • Drift not detected: Resources created with kubectl apply outside Git are tracked only if labelled with the Flux inventory.
  • Tenant escalation warnings: Always set serviceAccountName on tenant Kustomizations.