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.
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.
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
| Controller | Watches | Responsibility |
|---|---|---|
| source-controller | GitRepository, OCIRepository, HelmRepository, Bucket | Pulls source artefacts and exposes them as tarballs over HTTP for other controllers |
| kustomize-controller | Kustomization | Builds Kustomize overlays and applies them to the cluster |
| helm-controller | HelmRelease | Installs/upgrades Helm charts using HelmRepository or OCI sources |
| notification-controller | Provider, Alert, Receiver | Sends events to external systems and accepts webhooks for instant reconciliation |
| image-reflector | ImageRepository, ImagePolicy | Scans container registries for new tags matching policies |
| image-automation | ImageUpdateAutomation | Commits 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
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.
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
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.
| Dimension | Flux CD | Argo CD |
|---|---|---|
| Architecture | Multiple specialised controllers (toolkit) | Monolithic application server + UI |
| UI | None built-in (third-party: Weave GitOps, Capacitor) | Rich web UI included |
| App definition | Kustomization + HelmRelease CRDs | Application + ApplicationSet CRDs |
| Image automation | First-class, commits back to Git | Add-on (Argo Image Updater), commits or annotations |
| Multi-cluster | Hub-and-spoke or pull-per-cluster | Hub-and-spoke (Argo CD on management cluster) |
| Secrets | Native SOPS, Sealed Secrets, External Secrets via add-on | External Secrets, Vault plugin, Sealed Secrets |
| Mental model | Compose Lego bricks | Use the product |
| Best fit | Platform teams who want flexibility, dev portals as UI | Teams 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 applyoutside Git are tracked only if labelled with the Flux inventory. - Tenant escalation warnings: Always set
serviceAccountNameon tenant Kustomizations.