Flux
Flux is a CNCF-graduated GitOps engine for Kubernetes — a small family of controllers that live inside your cluster, pull declarative state from Git, OCI registries, Helm repositories or S3-style buckets, and reconcile the cluster to match it forever. It solves the platform problem of “how does anything get deployed here, safely, repeatably, and without handing cluster credentials to a CI pipeline?” — and because it’s a toolkit of composable APIs rather than a single product, it’s the piece platform teams reach for when they want to build their own delivery abstractions on top.
Imagine a team of tiny helpers who live inside your cluster. One helper’s only job is to go fetch things — it walks to the Git repo (or the registry, or the bucket), grabs a copy of the instructions, and puts the bundle on a shelf. A second helper reads the bundle and lays out plain Kubernetes objects. A third helper reads it and installs Helm charts. A fourth helper shouts in Slack when something breaks. Two more helpers watch the container registry and, when a shiny new version appears, they go and edit the Git repo for you. Nobody ever runs a deploy command. You change the instructions; the helpers do the rest. That team of helpers is Flux.
What Flux is and the problem it solves
☺ Like you’re 10: Instead of a person pushing changes into the cluster, Flux is a helper inside the cluster that keeps pulling changes out of Git.
Before a GitOps engine, deployment was a push: a pipeline held a kubeconfig, ran kubectl apply or helm upgrade, and hoped. Three problems followed it everywhere. Your CI system needed god-level cluster credentials, reachable from the internet. Nothing corrected drift between pipeline runs, so the cluster slowly diverged from anything anyone had written down. And there was no single authoritative answer to “what is supposed to be running in prod right now?” Flux inverts all three: the credentials stay inside the cluster, the loop never stops, and Git is the answer.
A toolkit, not a product
The single most important thing to understand about Flux is its shape. Argo CD gives you one big concept — the Application — plus a polished web UI wrapped around it. Flux gives you a handful of small, single-purpose controllers with their own CRDs, collectively called the GitOps Toolkit, and expects you to compose them. There is no official Flux UI. That sounds like a downside until you’re a platform team trying to build a paved road: composable Kubernetes APIs are exactly what you want underneath your own abstractions, as the Platform APIs & Operators lesson explains.
Flux separates “where does the config come from?” (a source) from “what do I do with it?” (a reconciler). One GitRepository can feed twenty Kustomizations and a dozen HelmReleases. That separation is the whole design, and it’s why Flux scales down to a demo cluster and up to a fleet without changing shape.
Flux v1 vs Flux v2 — mind the vocabulary
You will still find blog posts about Flux v1, a single monolithic daemon driven by the fluxctl CLI and by annotations on workloads, with Helm handled by a separate companion project (the Helm Operator, whose HelmRelease lived in a different API group entirely). Both are end-of-life. Flux v2 — usually just “Flux”, the graduated CNCF project — is the toolkit described here, driven by the flux CLI and by CRDs in the *.toolkit.fluxcd.io API groups. If an answer mentions fluxctl, “the Flux daemon”, a standalone Helm Operator, or annotation-driven automation on a Deployment, it is describing something that no longer exists.
Pull, not push — and why that matters
The defining property is the direction of travel. Nothing outside the cluster ever calls the Kubernetes API on your behalf: the controllers run inside, reach out to Git or a registry, and apply what they find using their own in-cluster ServiceAccount. Your CI system therefore needs no kubeconfig, no VPN into the control plane, and no long-lived cluster admin token — it only needs push access to a registry and a repo. That single inversion is what makes the four OpenGitOps principles (declarative, versioned and immutable, pulled automatically, continuously reconciled) achievable rather than aspirational, and it is the property exam scenarios keep circling back to.
Where Flux fits in a platform
☺ Like you’re 10: Flux sits in the “delivery” part of the platform — the bit that gets things onto the cluster — not the bit that builds them or watches them.
On the plane diagram in Platform Architecture, Flux sits squarely in the delivery / control plane: it is the thing that turns declared intent into running workloads on the Kubernetes substrate. It is emphatically not a CI system — it does not build images, run unit tests, or execute arbitrary scripts. Keep that boundary crisp, because blurring it is the classic anti-pattern catalogued in Anti-Patterns.
Its neighbours on the paved road
Upstream of Flux sits your CI (Tekton, GitHub Actions, whatever) which builds an image, signs it, and pushes it to a registry. Flux takes over from there. Beside it sit Kustomize and Helm — Flux does not replace them, it drives them: kustomize-controller embeds the Kustomize build engine, and helm-controller embeds the Helm SDK. Downstream, Flagger (from the same maintainers) takes the workload Flux just deployed and rolls it out progressively. Secrets come from External Secrets or in-repo SOPS, never from plaintext YAML. And Crossplane claims can be reconciled by Flux just like any other manifest, which is how “infrastructure as data” meets GitOps.
CNPE domain relevance
Flux is named on the official CNPE tool list and lands mostly in Domain 2 — GitOps & Continuous Delivery (25%), which is tied with Platform APIs & Self-Service for the largest slice of the exam blueprint. But it bleeds into others: reconciling cluster add-ons touches infrastructure provisioning, its Alert/Provider resources touch Observability, and its namespaced tenancy model touches Governance. See the Tool Landscape for how it sits among the official fifteen.
How it works — the GitOps Toolkit
☺ Like you’re 10: Six helpers, each with one job, all talking to each other through Kubernetes objects instead of phone calls.
Flux installs into a namespace (conventionally flux-system) as a set of Deployments. Each is an ordinary Kubernetes controller watching its own CRDs and writing status back. They coordinate only through the Kubernetes API and through artifacts — archives that source-controller fetches, verifies with a checksum, and serves over an in-cluster HTTP endpoint. No controller ever clones Git twice.
The six controllers and what each owns
Learn this table as a mapping from job to CRD. Under exam pressure the useful reflex is the reverse lookup: you are shown a failing HelmRelease, so you know to read helm-controller logs; you are shown a source that never becomes Ready, so you read source-controller. The controllers are independent Deployments and can fail independently — one being unhealthy does not stop the others.
| Controller | Job | CRDs it owns |
|---|---|---|
source-controller | Fetch and verify config from anywhere; publish it as an artifact | GitRepository, OCIRepository, HelmRepository, HelmChart, Bucket |
kustomize-controller | Build the manifests (Kustomize or plain YAML) and apply them; prune; health-check | Kustomization |
helm-controller | Install / upgrade / roll back Helm releases declaratively | HelmRelease |
notification-controller | Outbound alerts to Slack/Teams/etc., and inbound webhooks that trigger reconciles | Alert, Provider, Receiver |
image-reflector-controller | Scan container registries, catalogue tags, pick the “latest” by policy | ImageRepository, ImagePolicy |
image-automation-controller | Write the chosen tag back into Git and push a commit | ImageUpdateAutomation |
What one reconciliation actually does
Follow a single change through the machine. Someone merges a commit. Within its interval, source-controller notices the new revision, fetches it, produces an artifact and records the revision (branch and commit SHA) in the source’s .status. Every Kustomization whose sourceRef points at that source sees a new revision on its next tick, pulls the artifact, runs a Kustomize build over its path, and applies the result server-side. If prune is on, objects that were previously applied by this Kustomization but are no longer in the build get deleted. If wait is on, the controller then blocks until the applied objects report Ready or the timeout expires. Finally it writes a Ready condition and emits a Kubernetes event, which notification-controller may forward onwards.
Two consequences fall out of that sequence and both show up in troubleshooting. First, every stage is visible: kubectl get gitrepository,kustomization,helmrelease -A shows you where the chain stalled without reading a single log line. Second, nothing is instant — the worst-case latency is the sum of the intervals along the chain, which is why flux reconcile --with-source and Receiver webhooks exist.
Bootstrap — Flux installs itself, into Git
flux bootstrap is the idiomatic install and it does something clever: it writes Flux’s own manifests into a path in your Git repo, applies them, and then creates a GitRepository + Kustomization pointing at that path. From that moment on Flux manages Flux. Upgrading is a commit; changing controller settings is a commit; the installation is itself under the audit trail. There is no imperative escape hatch and that’s the point.
“I don’t have a Flux login, because there isn’t one. My whole interface is a folder: clusters/prod/apps/checkout/. I bump the image tag in a PR, and about a minute later it’s live. If I want to know whether it landed, I run flux get kustomizations -A in the shared shell, or I read the Slack message the notification-controller posted. Honestly? Fewer buttons is fine by me.”
The resources you will actually write
☺ Like you’re 10: Almost all of Flux is two objects: “here’s where the stuff lives” and “here’s what to do with it.”
Ninety percent of real Flux usage is a GitRepository or OCIRepository plus a few Kustomizations and HelmReleases. Learn these three shapes cold and you can read any Flux repo on earth.
Flux promotes its APIs group by group as they stabilise, so the exact version suffix depends on the Flux release installed. The examples below use versions current at the time of writing, but never guess in a task: run kubectl api-resources --api-group=source.toolkit.fluxcd.io (and the same for kustomize., helm., notification. and image.toolkit.fluxcd.io) to see the exact group, version and short names the cluster serves, then kubectl explain that kind for its real schema. The kinds and field names are stable; only the version suffix moves.
A source plus a Kustomization
The Kustomization is Flux’s workhorse. Note that it is not the same thing as a kustomization.yaml file — it’s a CRD in the kustomize.toolkit.fluxcd.io group that runs a Kustomize build over a path in a source and applies the result. The fields below are the ones you’ll set nearly every time.
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: platform-config
namespace: flux-system
spec:
interval: 1m # how often to re-check the remote for new commits
url: https://github.com/acme/platform-config.git
ref:
branch: main # or tag: / semver: / commit:
secretRef:
name: platform-config-auth # basic-auth or SSH key Secret
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: checkout
namespace: flux-system
spec:
interval: 10m # re-apply cadence even without a new commit (drift correction)
retryInterval: 2m # back-off after a failed apply
sourceRef:
kind: GitRepository
name: platform-config
path: ./apps/checkout/overlays/prod
prune: true # delete cluster objects removed from Git
wait: true # block until ALL applied objects report Ready
timeout: 5m
targetNamespace: checkout
dependsOn:
- name: infrastructure # do not apply until the add-ons Kustomization is Ready
healthChecks: # named readiness gates; IGNORED while wait is true
- apiVersion: apps/v1
kind: Deployment
name: checkout
namespace: checkout
postBuild:
substitute:
cluster_region: eu-west-1 # replaces ${cluster_region} in the built manifests
substituteFrom:
- kind: ConfigMap
name: cluster-vars
- kind: Secret
name: cluster-secrets
optional: trueTwo fields deserve a second look because they are routinely confused. wait: true is the blunt instrument: the controller waits for every object it applied to become Ready, and while it is set the healthChecks list is ignored. healthChecks is the scalpel: leave wait unset and name only the specific objects whose readiness actually gates you — useful when a Kustomization applies a hundred objects but only one Deployment matters for ordering. Pick one or the other deliberately; setting both and expecting the list to narrow the wait is a common misreading. The other pair worth internalising is interval versus retryInterval: interval is the steady-state re-apply cadence that corrects drift even when Git has not changed, while retryInterval is the shorter back-off used only after a failed reconciliation.
postBuild.substitute runs an envsubst-style pass over every byte of the built manifests. If you have a ConfigMap containing a shell script, an nginx config with $host, or a Prometheus rule with $labels, Flux will happily replace it with an empty string. Escape a literal dollar as $$ (so $${host} renders as ${host}), or don’t enable postBuild on that Kustomization at all. This bites almost everyone once.
A HelmRelease with remediation
HelmRelease (API group helm.toolkit.fluxcd.io/v2) is declarative Helm: you describe the chart, the version range and the values, and helm-controller decides whether that means an install, an upgrade, or nothing. The remediation blocks are what make it safer than a pipeline running helm upgrade: a failed upgrade can automatically retry and then roll back, without a human.
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: bitnami
namespace: flux-system
spec:
interval: 1h
url: https://charts.bitnami.com/bitnami
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: redis
namespace: flux-system
spec:
interval: 10m
releaseName: redis
targetNamespace: checkout
chart:
spec:
chart: redis
version: "20.x" # semver range — resolved by source-controller
sourceRef:
kind: HelmRepository
name: bitnami
interval: 1h # how often to look for a newer matching chart version
install:
createNamespace: true
remediation:
retries: 3
upgrade:
remediation:
retries: 3
remediateLastFailure: true # roll back to the last good release if retries are exhausted
driftDetection:
mode: enabled # correct manual edits to Helm-managed objects
values:
architecture: standalone
auth:
existingSecret: redis-auth # reference a Secret — never inline the passwordSecrets deserve the same discipline here as anywhere else in GitOps: use valuesFrom or existingSecret references, and keep the plaintext in Vault or a cloud secret manager via External Secrets, or encrypted in-repo with SOPS (which kustomize-controller can decrypt natively via spec.decryption).
Automated image tag updates
This is Flux’s signature extra, and it has no first-party equivalent in Argo CD. Three objects: ImageRepository scans a registry, ImagePolicy picks a winner from the scanned tags, and ImageUpdateAutomation rewrites the chosen tag into your manifests and pushes a commit. The link between the policy and the YAML is a marker comment next to the image field.
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: checkout
namespace: flux-system
spec:
image: ghcr.io/acme/checkout
interval: 5m
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: checkout
namespace: flux-system
spec:
imageRepositoryRef:
name: checkout
filterTags:
pattern: '^main-[a-fA-F0-9]+-(?P<ts>.*)' # only build tags from main
extract: '$ts' # sort on the captured timestamp
policy:
numerical:
order: asc # highest timestamp wins
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageUpdateAutomation
metadata:
name: checkout-auto
namespace: flux-system
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: platform-config
git:
checkout:
ref: { branch: main }
commit:
author: { name: fluxcdbot, email: fluxcdbot@acme.example }
messageTemplate: 'chore(deploy): {{range .Updated.Images}}{{println .}}{{end}}'
push:
branch: main # or a separate branch to open a PR from
update:
path: ./apps/checkout
strategy: SettersAnd in the Deployment that Flux will edit, the marker tells the automation exactly which field to rewrite:
spec:
containers:
- name: checkout
image: ghcr.io/acme/checkout:main-9f2a1c-1727101800 # {"$imagepolicy": "flux-system:checkout"}Image automation still goes through Git. Flux doesn’t patch the live Deployment — it commits the new tag, and then the ordinary reconcile loop picks it up. Your audit log, your revert story and your four OpenGitOps principles all stay intact. Push to a side branch instead of main and you get an auto-raised PR for environments that need review.
Alerts, providers and receivers
Finally, the notification plumbing — three small objects that turn a silent controller into something your on-call can live with. A Provider is a destination, an Alert is a subscription filter, and a Receiver is an inbound webhook endpoint that lets your Git host tell Flux “new commit!” instead of waiting out the 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 # key: address
---
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
name: on-call
namespace: flux-system
spec:
providerRef: { name: slack }
eventSeverity: error # or "info" for every reconcile
eventSources:
- kind: Kustomization
name: '*'
- kind: HelmRelease
name: '*'
---
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Receiver
metadata:
name: github-webhook
namespace: flux-system
spec:
type: github
events: [ "ping", "push" ]
secretRef: { name: receiver-token }
resources:
- kind: GitRepository
name: platform-configDay-to-day commands
☺ Like you’re 10: The flux command mostly just asks and nudges — it rarely changes anything itself.
The flux CLI is a thin, friendly wrapper over the CRDs. Everything it does you could do with kubectl; it just saves you a lot of typing and prints much better tables. Keep that in mind under exam pressure — if you can’t remember a flag, kubectl get kustomizations -A and kubectl describe always work.
Install and bootstrap
# Is this cluster's Kubernetes version supported, and are the prerequisites met? flux check --pre # A personal access token with repo scope; flux bootstrap reads it from the environment. export GITHUB_TOKEN=ghp_xxx # --path is where Flux writes and then manages its own manifests. # --personal targets a user-owned repo rather than an organisation repo. # --components-extra opts in to the two image-automation controllers, which are # NOT installed by default. flux bootstrap github \ --owner=acme \ --repository=platform-config \ --branch=main \ --path=./clusters/prod \ --personal \ --components-extra=image-reflector-controller,image-automation-controller # After bootstrap: are all controllers running, and which versions? flux check
Re-running flux bootstrap with the same arguments is safe and idempotent — it is also how you upgrade, because a newer flux CLI writes newer controller manifests into the same path and commits them. There are equivalent subcommands for other Git hosts (flux bootstrap gitlab, flux bootstrap bitbucket-server, and a host-agnostic flux bootstrap git that takes a plain URL plus SSH or token credentials). If you cannot remember a bootstrap flag under pressure, flux bootstrap github --help is on the exam desktop and the doc site is not.
Look, then nudge
# The one command to start every investigation: flux get all -A # sources, kustomizations, helmreleases + Ready/Suspended flux get sources git -A flux get kustomizations -A --status-selector ready=false flux get helmreleases -A # Stop waiting for the interval and reconcile right now. # --with-source pulls the source first, then reconciles what depends on it: flux reconcile source git platform-config flux reconcile kustomization checkout --with-source flux reconcile helmrelease redis --with-source # Pause and resume — the safe way to hand-edit during an incident: flux suspend kustomization checkout flux resume kustomization checkout
Debug and trace
flux logs --level=error --all-namespaces # controller logs, already filtered flux logs --kind=Kustomization --name=checkout --follow flux events --for Kustomization/checkout # Kubernetes events for one object flux tree kustomization checkout # every object this Kustomization owns # "Where on earth did this live object come from?" — the killer command: flux trace deployment/checkout -n checkout # Dry-run a change before you commit it: flux diff kustomization checkout --path ./apps/checkout/overlays/prod # Generate manifests instead of applying imperatively (GitOps-friendly): flux create source git podinfo \ --url=https://github.com/stefanprodan/podinfo --branch=master \ --interval=1m --export > podinfo-source.yaml flux create kustomization podinfo \ --source=GitRepository/podinfo --path="./kustomize" \ --prune=true --interval=10m --export > podinfo-kustomization.yaml
“flux trace is the one I actually remember. Something weird is running in my namespace, I point flux trace at it, and it tells me the exact repo, path, commit SHA and Kustomization that put it there. That question used to take an afternoon of Slack archaeology.”
Gotchas and failure modes
☺ Like you’re 10: Most “Flux is broken” moments are really “Flux is asleep”, “Flux is waiting”, or “Flux couldn’t read your folder”.
The interval trap and the suspend trap
Newcomers assume Flux reacts instantly. It doesn’t — every object has an interval, and the worst case for a change to land is the sum of the intervals along the chain (source, then Kustomization, then any dependents). Set the GitRepository interval to about a minute and use a Receiver webhook for instant reaction; don’t set every interval to 10s, because you’ll hit Git provider rate limits and hammer the API server. Conversely, the very first thing to check when “nothing is deploying” is whether someone left a resource suspended during an incident and never resumed it — flux get all -A shows a SUSPENDED column for exactly this reason. See Triage: Delivery for the full decision tree.
Build failures, prune surprises and ownership
A Kustomization whose path is wrong, or whose kustomization.yaml references a file that doesn’t exist, fails at build time — before anything reaches the cluster — with a BuildFailed condition and a very readable message in flux get kustomizations. Reproduce it locally with kustomize build ./apps/checkout/overlays/prod before blaming the controller. Pruning has its own trap: kustomize-controller tracks ownership with labels (kustomize.toolkit.fluxcd.io/name and /namespace), so if you rename a Kustomization, the old one’s objects become orphans that nothing will ever garbage-collect. Rename deliberately, and clean up by hand.
dependsOn means “don’t start until the named Kustomization is Ready.” But if that dependency has wait: false and no healthChecks, it reports Ready the instant its objects are applied — not when they’re actually working. So your app Kustomization proceeds while cert-manager’s CRDs are still installing, and you get a stream of “no matches for kind” errors. If you depend on something, make sure that something has wait: true or explicit healthChecks. Missing-CRD ordering is the single most common Flux bootstrap failure.
Drift, immutability and stuck deletions
Flux applies with server-side apply and will fight you for any field it manages — that’s reconciliation working, and it’s the same surprise described in GitOps. Some conflicts are unwinnable, though: changing an immutable field (a Job’s spec.template, a Deployment’s selector) produces an endless apply error, and spec.force: true is the escape hatch that lets Flux delete-and-recreate — with downtime for that object. Deletions can hang too: prune a namespace whose finalizers are stuck and the Kustomization sits un-Ready forever, so see Triage: Workloads for finalizer-hunting.
On a kind cluster with a personal GitHub PAT: 1) flux bootstrap github into a fresh repo at ./clusters/dev and look at what Flux committed for itself. 2) Add the public podinfo repo as a GitRepository and a Kustomization with prune: true — use --export and commit the YAML rather than applying it. 3) kubectl scale the podinfo Deployment by hand and watch the next reconcile revert you. 4) Run flux suspend kustomization podinfo, edit it again, and confirm nothing happens — then resume and watch it snap back. 5) Break it on purpose: point path at a folder that doesn’t exist, then read the failure with flux get kustomizations and flux logs --level=error. That last step is worth more exam points than the first four.
Alternatives and when to choose it
☺ Like you’re 10: Argo CD is a finished product with a dashboard; Flux is a box of Lego for building your own.
The honest answer is that both Argo CD and Flux are CNCF-graduated, both implement all four OpenGitOps principles, and both will serve you well for a decade. Choose on shape and audience, not features.
Argo CD versus Flux, dimension by dimension
Read the table by column if you are choosing, and by row if you are answering an exam question about a specific capability. The third column is there as a reminder of what you are actually replacing — most teams adopting a GitOps engine are migrating from a pipeline that holds a kubeconfig, and every row is a thing that pipeline cannot do.
| Dimension | Flux | Argo CD | CI-driven kubectl apply |
|---|---|---|---|
| Shape | Toolkit of six composable controllers | One integrated, application-centric product | A script in a pipeline |
| Core objects | GitRepository + Kustomization / HelmRelease | Application / ApplicationSet | None — imperative |
| UI | None official; CLI plus third-party dashboards | Rich built-in web UI with live diffs | The pipeline’s log output |
| Tenancy model | Namespaced CRs + plain Kubernetes RBAC (and spec.serviceAccountName impersonation) | AppProjects, its own RBAC layer, SSO | Whoever holds the kubeconfig |
| Helm handling | Native HelmRelease via the Helm SDK — real releases, rollbacks, tests | Renders charts to manifests by default | helm upgrade, no drift correction |
| Image automation | Built in (reflector + automation controllers) | Separate Argo CD Image Updater project | Whatever you script |
| Drift correction | Continuous, always on | Continuous, selfHeal opt-in | None |
| Best when | Platform teams composing their own APIs; heavy Helm/OCI use; strong Kubernetes-RBAC story wanted | Many developers need self-serve visibility into sync status and diffs | One tiny app, one cluster, low stakes |
How to pick — and why running both is defensible
A practical tie-breaker: if your primary users are developers who want to look at a screen, Argo CD’s UI is a genuine developer-experience feature and buying it costs you nothing. If your primary user is the platform team itself, composing higher-level abstractions behind a portal or a set of custom APIs, Flux’s small namespaced CRDs compose more cleanly and generate less to hide. Running both — Flux reconciling cluster add-ons and infrastructure, Argo CD serving app teams their own dashboards — is a common and perfectly respectable answer, provided you draw a hard line about which engine owns which namespaces. Two reconcilers fighting over one object is a genuinely miserable outage.
Beware the fake tie-breakers. “Flux has no UI” matters far less than it sounds when every object is a CR with a status that Grafana or Backstage can render. “Argo CD is easier” is usually a statement about day one, not day four hundred. Scale, multi-tenancy and how well the engine composes with the rest of your reference architecture are the criteria that survive contact with production.
What Flux is not an alternative to
Exam questions like to test the boundary, so be precise about what Flux does not replace. It is not a CI system: it never builds, tests or signs an image, so Tekton or a hosted CI still sits upstream. It is not a progressive-delivery controller: Kustomization and HelmRelease do all-at-once rollouts, and canaries or blue-green need Flagger or Argo Rollouts layered on top. It is not a templating language: it drives Kustomize and Helm rather than competing with them. It is not a secret manager: it can decrypt SOPS-encrypted files in-repo, but sourcing secrets from a vault is External Secrets’ job. And it is not a policy engine — admission control stays with Kyverno or Gatekeeper, which is exactly where you want it, because a policy that only runs in the delivery tool is trivially bypassed.
Foxy: No dashboard? How do you even see what’s deployed? That feels like a step backwards.
Benny: Everything is a Kubernetes object with a status. flux get all -A is the dashboard. And if you want pixels, point Grafana or Backstage at the same statuses — they’re just CRs.
Recon: BEEP. Six of me, actually. One fetches, one kustomizes, one helms, one shouts, two watch the registry. We only talk through the API server. Very tidy.
Gizmo: Prod is on fire, so I ran flux suspend on everything and hot-patched a Deployment. Crisis solved! I’ll resume it… eventually. 🤑
Timmy: Suspending during an incident is correct, Gizmo. Not resuming is how you get a cluster nobody can explain three weeks later. Put the fix in Git, then resume.
Dot: Meanwhile the image automation already opened the PR that bumps my tag, so I just… approve it. That part I genuinely love.
Exam relevance and going further
☺ Like you’re 10: Flux is on the exam’s tool list — but its own website is not open during the test, so the shapes have to be in your head.
Flux is one of the fifteen officially named CNPE tools, and the GitOps & Continuous Delivery domain carries 25% of the exam — tied with Platform APIs & Self-Service for the heaviest weighting. Expect to be asked to do something, not describe it: point a reconciler at a repo path, turn on pruning, make one thing wait for another, or explain why a resource is stuck.
The documentation allowlist — read this twice
During the CNPE the only permitted documentation is kubernetes.io/docs, kubernetes.io/blog, any task-specific docs explicitly linked in the exam’s Quick Reference box, and local man pages / /usr/share docs on the exam desktop. Flux is not Kubernetes, so fluxcd.io is off-limits unless a specific task’s Quick Reference links it. That means GitRepository, Kustomization and HelmRelease have to come out of your memory or out of kubectl explain. Practise writing them from a blank file, and drill the shapes on Know Cold.
⚖ CNPA vs CNPE — That allowlist is a CNPE-specific mechanic; CNPA has no allowlist at all, because CNPA is fully closed-book — zero external resources, zero lookups of any kind, on anything. That’s stricter than CNPE, not looser. Even so, the reconciliation concepts on this page — sources, Kustomizations, the pull-based GitOps loop — are worth knowing cold, since this concept-level knowledge still matters for CNPA’s closed-book recall.
Your on-cluster fallbacks are genuinely good, so rehearse them: kubectl api-resources | grep toolkit lists every Flux kind and its short name; kubectl explain kustomization.spec --recursive prints the entire schema from the CRD itself; and flux create ... --export generates a valid skeleton you can edit rather than typing YAML from scratch. Those three habits substitute for the doc site almost completely.
What to be able to do cold
Without notes, you should be able to: bootstrap or install Flux; write a GitRepository and a Kustomization with path, prune, interval and wait; order two Kustomizations with dependsOn; write a HelmRelease against a HelmRepository; force an immediate sync with flux reconcile --with-source; suspend and resume; and diagnose a not-Ready resource from flux get, flux events and flux logs. Rehearse the whole loop in Practice: GitOps and the timed drills in Practice Tasks, then use the Command Reference as your cheat sheet and the Glossary for the vocabulary. If Flux’s place among the other fourteen named projects is still fuzzy, go back to the Tool Landscape.
Official sources — for study time, not exam time
Read these before the exam: the project documentation at fluxcd.io/flux, the component API references at fluxcd.io/flux/components, the multi-tenancy and repository-structure guidance at fluxcd.io/flux/guides/repository-structure, the source of truth for CRDs at github.com/fluxcd/flux2, and the vendor-neutral principles at opengitops.dev. For the wider picture, the CI/CD & Progressive Delivery lesson shows what feeds Flux and what takes over after it.
1. Name the six GitOps Toolkit controllers and one CRD each owns. 2. What is the difference between a kustomization.yaml file and a Flux Kustomization resource? 3. Your Kustomization has dependsOn: infrastructure but still fails with “no matches for kind Certificate” — what’s missing? 4. Which command tells you the repo, path and commit that produced a live Deployment? 5. Name two things Flux has built in that Argo CD needs a separate project for. 6. During the exam, can you open fluxcd.io?
Check your answers
source-controller(GitRepository/OCIRepository/HelmRepository/Bucket),kustomize-controller(Kustomization),helm-controller(HelmRelease),notification-controller(Alert/Provider/Receiver),image-reflector-controller(ImageRepository/ImagePolicy),image-automation-controller(ImageUpdateAutomation).- A
kustomization.yamlis a plain file read by the Kustomize build engine. A FluxKustomizationis a CRD inkustomize.toolkit.fluxcd.iothat tells kustomize-controller which source and path to build, then applies, prunes and health-checks the result on an interval. Same word, different layer. - The
infrastructureKustomization almost certainly haswait: falseand nohealthChecks, so it reports Ready as soon as its objects are applied — before cert-manager’s CRDs are established. Setwait: true(or add explicithealthChecks) on the dependency. flux trace deployment/<name> -n <namespace>.- Automated image tag updates (Argo CD uses the separate Image Updater project), and native Helm release management with rollback/remediation via
HelmRelease. - No. Only
kubernetes.io/docs,kubernetes.io/blog, docs linked in a task’s Quick Reference box, and localman//usr/sharedocs. Fall back tokubectl explain,kubectl api-resourcesandflux create --export.