Rules¶
Every finding carries a rule ID. fluxlint explain <id> prints the same text as this page.
Change a rule's severity, or turn it off, under rules: in .fluxlint.yaml.
Graph¶
Whether the components can converge at all: references, ownership and ordering.
FL-G001 dangling-reference¶
Default severity: error
A dependsOn or sourceRef names an object that nothing in the repository defines. Flux reports the dependent as not ready forever.
FL-G002 bootstrap-deadlock¶
Default severity: error
The precedence graph has a cycle, so the repository cannot converge from an empty cluster. Each edge of the cycle is listed with its reason; remove or reverse one.
FL-G003 dual-ownership¶
Default severity: error
The same object is rendered by more than one Flux Kustomization. They overwrite each other on every reconcile, and pruning one deletes the object for both. Objects applied to different clusters (spec.kubeConfig) are different objects. A Namespace whose copies are identical is a warning: nothing is overwritten, but pruning either owner still deletes it.
FL-G004 missing-namespace¶
Default severity: error
An object is applied into a namespace that nothing creates. Declare it under externals.namespaces if it is created out of band.
FL-G005 unknown-crd¶
Default severity: warning
Custom resources of this API group are applied but no rendered component installs its CRDs. They may come from a Helm chart or external source fluxlint has not rendered; declare the group under externals.crdGroups to acknowledge.
FL-G008 build-failed¶
Default severity: error
kustomize build (or post-build substitution) failed for the path a Flux Kustomization points at.
FL-G006 unserved-version¶
Default severity: error
A custom resource uses an apiVersion that the CRD rendered for it does not serve. The apply fails with 'no matches for kind'.
Substitution¶
Post-build variables: where they come from and whether they resolve.
FL-S001 undefined-variable¶
Default severity: error
A ${var} has no definition in postBuild.substitute / substituteFrom and no default. Flux substitutes the empty string.
FL-S002 missing-substitute-source¶
Default severity: error
postBuild.substituteFrom names a ConfigMap/Secret that nothing renders and that is not optional. Declare it under externals.substitutions if it is created out of band.
FL-S004 unexpanded-variable¶
Default severity: info
${...} expressions appear in a Kustomization without postBuild, so they reach the cluster verbatim. Expected for shell snippets; a bug if Flux substitution was intended.
Validation¶
Whether the API server will accept what is rendered.
FL-V001 schema-violation¶
Default severity: error
A custom resource fails the OpenAPI schema of the CRD rendered for it, after defaulting — the same validation the API server performs. Fields the schema does not define are FL-V009.
FL-V003 invalid-builtin¶
Default severity: error
An object of a built-in API group is not valid for the Kubernetes release the cluster runs (kubeVersion, which can differ per entrypoint): a field that does not exist there, a value of the wrong type, a required field that is missing, or an API version that release does not serve. Every Kubernetes release publishes the OpenAPI schema of its APIs; fluxlint fetches the one for your release once and caches it, so a field added in 1.36 is an error on a 1.35 cluster and fine on a 1.36 one. Set kubeVersion to the release you plan to upgrade to and the same check says what will break. Flux applies with server-side apply, which rejects all of these. When the schemas cannot be loaded (offline with a cold cache), the Kubernetes types bundled with fluxlint are used and the run says so.
FL-V004 invalid-value¶
Default severity: error
A built-in object has the right fields and types but a value the API server rejects: a selector that does not match the pod template, a port out of range or a port name over 15 characters, a volumeMount with no volume, a CronJob schedule that does not parse, a Service with two unnamed ports. Flux applies with a server-side dry run first, so one such object fails its whole Kustomization. The checks restate the API server's own validation and cover the common workload, Service, Ingress, RBAC and storage kinds; an object of another kind is not checked.
FL-V008 metadata-not-string¶
Default severity: error
A label value is not a string: YAML reads an unquoted 1.31, true or 0 as a number or a boolean. For anything Helm installs the API server rejects the object. For anything a Flux Kustomization applies the result is worse, because nothing fails: kustomize-controller reads the labels to add its own, the read fails as a whole on one non-string value, and it writes back only its own two. The object is applied with none of its labels and the Kustomization is Ready. A Namespace loses its Pod Security level this way, and anything that selects by the lost labels stops matching. Flux treats this as intended (fluxcd/flux2 issue 4968, fluxcd/pkg pull request 1208), so it has to be caught before the merge. Checked on every kind, custom resources included. Annotations are not checked: kustomize converts those to strings.
FL-V009 unknown-field¶
Default severity: error
A custom resource sets a field that its CRD, as rendered from the repository, does not define. The API server does not reject it: it prunes it, silently, and the object is applied without it. A misspelt field, or one from a newer version of the CRD than the cluster runs, has no effect and nobody is told. Each cluster is checked against the CRDs it installs, so a Flux Kustomization on a cluster with Flux 2.4 is checked against Flux 2.4's CRD. Parts of a schema marked x-kubernetes-preserve-unknown-fields accept anything and are not reported.
FL-V002 pod-security¶
Default severity: error
A pod template violates the Pod Security level its namespace enforces (pod-security.kubernetes.io/enforce), evaluated with the API server's own checks. The workload is accepted but its pods are never created. Also reports a Namespace whose pod-security.kubernetes.io labels the plugin cannot parse (a level that is not privileged, baseline or restricted; a version that is not latest or v1.x, such as 1.31 without the v): the API server refuses to create such a Namespace.
FL-V005 policy-violation¶
Default severity: error
A ValidatingAdmissionPolicy that the repository installs, bound with Deny, rejects an object that the repository renders. The policy is compiled and evaluated with the API server's own code: matchConstraints, matchConditions, variables, parameters that are in Git, namespaceObject, and request.userInfo set to the Flux controller's ServiceAccount. Every object is evaluated as a create. With --base, every object the change modifies is also evaluated as an update with oldObject, which is where immutability rules speak. A binding that only warns gives a warning; one that only audits is skipped. An expression that needs the authorizer, or a parameter that is not in Git, is listed as not evaluated.
FL-V006 policy-invalid¶
Default severity: error
A bound ValidatingAdmissionPolicy has an expression that does not compile. With failurePolicy: Fail, which is the default, the API server then rejects every request the policy matches.
FL-V007 kyverno-violation¶
Default severity: error
A Kyverno policy that the repository installs, in Enforce mode, rejects an object that the repository renders. Kyverno's engine cannot be linked into another program, so the policies are evaluated by the kyverno CLI, which is that engine: install the version your clusters run and the verdict is the one they would give. Nothing is downloaded or started; when the CLI is not on PATH the policies are listed as not evaluated. Failures of Audit policies, and rules that need the cluster (apiCall, configMap context), are suggestions. Policy exceptions in the repository are honoured. Policies that mutate or generate are not applied.
Runtime¶
Whether pods can start once their objects are applied.
FL-R001 unresolved-config-reference¶
Default severity: error
A pod references a Secret or ConfigMap (or a key of one) that nothing creates: not a manifest, an ExternalSecret, a ClusterExternalSecret selecting the namespace, a cert-manager Certificate, nor externals.secrets. The pod stays in ContainerCreating / CreateContainerConfigError.
FL-R002 unresolved-identity-reference¶
Default severity: error
A pod names a ServiceAccount or imagePullSecret that nothing creates in its namespace. Without the ServiceAccount, pods are not created. A missing imagePullSecret is a warning: the kubelet starts the pod anyway, and only an image that needs the Secret fails to pull.
FL-R003 positional-patch¶
Default severity: warning
A JSON patch in a Flux Kustomization addresses a list element by index (env/3, containers/0/args/2). When the upstream manifest adds or reorders entries — typically on a version bump — the patch silently applies to a different element. Prefer a strategic-merge patch keyed by name.
FL-R004 webhook-backend¶
Default severity: error
An admission webhook's Service does not exist, selects no pods, or fronts only workloads scaled to zero while the webhook fails closed.
FL-R007 module-skew¶
Default severity: warning
A controller rendered from a Git source is built against a newer version of an API module than the tag pinned for the component that installs that module's CRDs. Read from go.mod in both sources.
FL-R008 admission-window¶
Default severity: warning
A component applies objects that a fail-closed admission webhook intercepts, the webhook is installed by another component, and nothing makes the apply wait until the webhook answers. Between the webhook being registered and its pods serving, the API server rejects every matching request, so the apply fails and is retried a retryInterval later. dependsOn alone is not enough: a Kustomization without wait: true is Ready as soon as it is applied. Webhook rules, objectSelector and namespaceSelector are evaluated, and the webhooks Kyverno registers at runtime are worked out from its chart and its policies. Two cases are suggestions rather than warnings: a component applied before the webhook exists (its first apply is safe, a retry or the next interval is not), and a selector that needs a label which is not set in Git (controllers often set such labels at runtime). A webhook with matchConditions is not evaluated.
FL-R009 contested-field¶
Default severity: warning
The manifests set a field that another controller also writes: spec.replicas on a workload that a HorizontalPodAutoscaler or KEDA ScaledObject scales, or a caBundle that cert-manager is asked to inject. Flux takes the field back on every reconcile and the other controller changes it again. The first install looks healthy; the fight shows on the second reconcile.
FL-R010 unstable-render¶
Default severity: info
A Helm chart renders an object differently each time: a template calls randAlphaNum, genCA, now or a similar function. Helm renders again on every upgrade, so the object changes whenever the release is upgraded, whatever the upgrade was for: a generated password is replaced under a running database, a certificate is reissued, pods restart. Raised to a warning for Secrets and webhook configurations. A template that also calls lookup is not reported, because it usually keeps the value already in the cluster.
FL-R011 contested-secret¶
Default severity: error
Two ExternalSecrets create the same Secret in the same namespace and both want to own it, directly or through ClusterExternalSecrets whose namespaces overlap. The operator refuses the second with "already owned by another ExternalSecret"; it never becomes Ready, and neither does its ClusterExternalSecret. Namespaces are taken from spec.namespaces and from selectors evaluated against the namespaces in Git. target.creationPolicy Merge, Orphan and None do not take ownership and are not reported.
FL-R012 missing-issuer¶
Default severity: error
A cert-manager Certificate names an Issuer or ClusterIssuer that nothing in the repository creates. The Certificate is accepted and stays pending, the Secret it should produce never appears, and every pod that mounts it waits in ContainerCreating. A warning when some component could not be rendered and may create the issuer. Issuers of other API groups are not checked.
FL-R013 single-pod-gate¶
Default severity: warning
A fail-closed admission webhook that admits the applies of other components is served by exactly one pod. No ordering helps: whenever that pod restarts, is rescheduled or is upgraded, every matching apply in the cluster is rejected until it is back, and each rejected Kustomization waits a retryInterval. Webhooks that Kyverno registers at runtime are included, worked out from its chart and its policies. A warning when three or more other components go through the webhook, a suggestion below that. Workloads under an autoscaler, and DaemonSets, are taken to have more than one pod.
FL-R014 source-credentials¶
Default severity: error
A GitRepository, OCIRepository, HelmRepository or Bucket names a Secret (secretRef, certSecretRef, proxySecretRef) that nothing creates: not a manifest, an ExternalSecret, a ClusterExternalSecret selecting the namespace, nor externals.secrets. The source never becomes Ready and everything that reads from it waits. The repository's own source is left out: flux bootstrap creates its Secret in the cluster. A warning when some component could not be rendered and may create it.
FL-R015 values-reference¶
Default severity: error
A HelmRelease's valuesFrom names a Secret or ConfigMap, or a key of one (valuesKey, default values.yaml), that nothing creates. helm-controller cannot compose the values ("could not resolve Secret chart values reference … key not found"), so the release is never installed. Producers are manifests, ExternalSecrets, ClusterExternalSecrets selecting the namespace, Certificates and externals.secrets; one that does not list its keys (an ExternalSecret with dataFrom) is taken to have any key. Entries marked optional are skipped.
Contracts¶
What a controller or chart declares it cannot run without.
FL-C001 contract-unmet¶
Default severity: error
A component ships a fluxlint-contract.yaml that says what it cannot run without: CRDs it watches, Secret and ConfigMap keys it reads, environment variables and flags its containers need. This repository does not provide it, provides it without ordering, or passes a flag this version does not accept.
Assertions¶
Your own rules, written in CEL.
FL-A001 assertion-failed¶
Default severity: error
A repository-specific assertion from .fluxlint.yaml does not hold for a rendered object.
FL-A002 assertion-invalid¶
Default severity: error
An assertion in .fluxlint.yaml does not compile or does not evaluate to a bool.
Transitions¶
What reconciling a change does to a cluster that runs the base. Needs --base.
FL-D001 prune¶
Default severity: info
Objects present at the base commit are gone, and their Kustomization prunes: Flux will delete them. Raised to a warning when namespaces, CRDs, PVCs, StatefulSets or Secrets are among them.
FL-D002 immutable-change¶
Default severity: error
The change updates a field the API server treats as immutable (a workload selector, a StatefulSet's volumeClaimTemplates, a Job template, a RoleBinding's roleRef …). The apply is rejected unless the Kustomization sets force: true, which deletes and recreates the object.
FL-D003 ownership-move¶
Default severity: warning
An object moves from one Flux Kustomization to another. If the old owner prunes and reconciles after the new owner has applied, the object is deleted and only comes back on the next reconcile.
FL-D004 orphaned¶
Default severity: info
Objects leave Git but their Kustomization has prune: false, so they remain in the cluster with nothing managing them.
Sources and rendering¶
What fluxlint could not fetch or does not model.
FL-X001 source-unavailable¶
Default severity: warning
A Kustomization reads from another repository or artifact that could not be materialised, so nothing it applies was analysed. Run without --offline, fix access, or map it with sources.overrides. Raised to an error when the host answered and the ref, tag or chart version is not there: Flux will not find it either, and everything that waits for the component times out.
FL-X002 floating-ref¶
Default severity: info
A source follows a branch or semver range. What Flux applies can change without a commit to this repository, and fluxlint's result reflects whatever was fetched last.
FL-X003 render-gap¶
Default severity: info
Part of a component's spec is not modelled, so what fluxlint analysed may differ from what the controller applies.
FL-X004 image-not-found¶
Default severity: error
A pod template names a container image that its registry does not have: a tag that was never pushed, a typo, a Git tag mistaken for an image tag. With images.platforms set, also an image that is not built for your nodes. The pod stays in ImagePullBackOff, and a Kustomization that waits for it times out. Only images matching images.verify are looked up, with the credentials of your Docker config; an image that could not be checked is a render note, not a finding. Scaled-to-zero workloads are reported as a warning: nothing pulls the image until they are scaled up.
Timing¶
How long a bootstrap can take, and why.
FL-T001 critical-path¶
Default severity: info
The chain of waits that determines the worst-case time for a bootstrap to converge. A bound, not a prediction.
FL-T002 dominant-delay¶
Default severity: info
One component contributes a large share of the worst-case bound, usually a generous timeout underneath a wait: true parent.
FL-T004 unjustified-dependency¶
Default severity: info
A dependsOn edge for which no import exists between the two subtrees. It may encode a runtime need fluxlint cannot see; if not, it only serialises reconciliation.
FL-T005 redundant-dependency¶
Default severity: info
A dependsOn edge already implied by another dependsOn path.
FL-T006 implicit-ordering¶
Default severity: warning
A component imports something from another without any declared ordering. It converges only by failing and retrying.
FL-T007 retry-cliff¶
Default severity: warning
No retryInterval and a long interval: one failed apply stalls the component for a full interval.
FL-T008 timeout-inversion¶
Default severity: warning
A wait: true parent times out before the components it waits for are allowed to, so it flaps NotReady while they legitimately converge.
FL-T010 stale-substitution¶
Default severity: warning
A Kustomization substitutes variables from a ConfigMap or Secret that another Kustomization of the same source applies, with no dependsOn between them. Both reconcile when a commit arrives; if the reader goes first it uses the old values and is not triggered again until its interval elapses. With a dependsOn, kustomize-controller waits until the dependency has applied the same revision.
FL-T100 bootstrap-budget¶
Default severity: error
The worst-case bootstrap bound exceeds timing.maxBootstrapBound.
Observed¶
Only with --cluster-state: where a real cluster and fluxlint disagree about the same commit.
FL-O001 unpredicted-failure¶
Default severity: error
With --cluster-state: a Kustomization or HelmRelease is not Ready in a real cluster, and no error or warning named it. This is a gap in what fluxlint models. Failures that only repeat another (a dependency or a child that is itself not Ready) are not reported. Read the cluster's message, fix the cause, and consider an assertion or a contract so that the next occurrence is found before the cluster is.
FL-O002 unconfirmed-finding¶
Default severity: info
With --cluster-state: fluxlint reports an error on a component that is Ready in a real cluster. Either the finding is a false positive, or the cluster was helped by something outside Git, such as a test script that seeds a Secret.
FL-O003 not-compared¶
Default severity: info
With --cluster-state: components that exist on one side only, and components that failed in the cluster but could not be rendered, so there is no verdict either way. Suspended objects are left out.