Flux 2.10.0 came out on 26 October 2026. It is a feature release, and for most clusters the upgrade is the usual routine of regenerating the components manifest and letting Flux apply it. A few setups break, though, and they break quietly. Sources stop authenticating, alerts lose their summary, and image automation rescans every registry.
This guide covers what changed, how to tell whether your cluster is affected, and an upgrade order that keeps everything running. It assumes you run Flux 2.9 today, installed with flux bootstrap or with the components manifest committed to Git.
What ships in 2.10
The release bundles source-controller v1.10.1, kustomize-controller v1.10.1, notification-controller v1.10.0, helm-controller v1.7.1, image-reflector-controller v1.3.0, image-automation-controller v1.3.0 and source-watcher v2.3.1. Everything is built with Kubernetes 1.37 and Go 1.27, and helm-controller uses Helm 4.3.1.
Flux supports the three latest Kubernetes minor versions, so 2.10 runs on 1.35, 1.36 and 1.37. The release notes set patch minimums too, 1.35.2 and 1.36.1. If you are still on 1.34, upgrade Kubernetes first.
The headline feature is object identity. In 2.9 it was called object-level workload identity and sat behind the ObjectLevelWorkloadIdentity feature gate. It lets each Flux object authenticate to a cloud service or registry as its own Kubernetes ServiceAccount, instead of sharing the controller's identity. In 2.10 it is generally available, the gate is retired and the feature is always on. The notification APIs (Event, Alert and Provider) also reach v1.
The four breaking changes
Token creation is no longer in the default RBAC
Object identity works by asking the Kubernetes TokenRequest API for a token on behalf of the ServiceAccount named in the Flux object. That requires the create verb on serviceaccounts/token. Up to 2.9, the crd-controller ClusterRole that Flux installs granted it to every controller. In 2.10 it is gone.
kustomize-controller and helm-controller are unaffected because they are bound to cluster-admin. source-controller, notification-controller and the two image controllers are not. If any of your GitRepository, OCIRepository, Bucket, ImageRepository or notification Provider objects use spec.serviceAccountName to get cloud credentials, they will start failing as soon as the new RBAC lands. This is the change most likely to page someone, and it has to be handled before the upgrade.
Alert and Provider move to v1
notification.toolkit.fluxcd.io/v1 now serves Alert and Provider, and it is the storage version. v1beta3 is still served but deprecated. The catch is in conversion. A few deprecated fields don't exist in v1, so they are dropped when an object is converted. These are Alert.spec.summary, Provider.spec.interval, Provider.spec.proxy, the static JWT for Azure Event Hub, and TLS or proxy settings stored in the Secret behind Provider.spec.secretRef. Their replacements are spec.eventMetadata.summary, spec.proxySecretRef and spec.certSecretRef.
Nothing errors when a field is dropped. Your Slack messages just lose their summary line, or a Provider behind a corporate proxy stops reaching the internet.
Image tags move to the artifact storage backend
image-reflector-controller used to keep scanned tags in BadgerDB. In 2.10 the FluxStorage feature gate is on by default, and tags go into the same artifact storage the other controllers use. Switching backends wipes the local tag database, so the first reconciliation after the upgrade rescans every ImageRepository. With a few repositories that is harmless. With hundreds, it is a burst of registry requests that can hit rate limits, Docker Hub's in particular.
Remote cluster auth uses object identity
Kustomizations and HelmReleases that deploy to another cluster through spec.kubeConfig.configMapRef now authenticate through object identity. If that ConfigMap has the legacy audiences key and you want to use the new ServiceAccount annotations, replace it with the credential.auth.fluxcd.io/audiences annotation on the ServiceAccount. The two can't be combined.
Step 1: check the CLI and the cluster
Install the 2.10 CLI on the machine that will run the upgrade, then run the pre-flight check against each cluster. flux check --pre verifies the Kubernetes version and tells you if a newer Flux is available.
flux --version
flux check --pre
The CLI has to be 2.10 for the later steps, because flux migrate and flux install use the API versions and manifests built into the binary.
Step 2: find objects that use object identity
List every object the affected controllers reconcile that names a ServiceAccount. This command prints the kind, the namespace and name, and the provider.
kubectl get gitrepositories,ocirepositories,buckets,imagerepositories,providers -A -o json \
| jq -r '.items[] | select(.spec.serviceAccountName != null)
| "\(.kind) \(.metadata.namespace)/\(.metadata.name) \(.spec.provider // .spec.type)"'
Rows with aws, azure or gcp use object identity and need the token permission. An OCIRepository with the generic provider and a ServiceAccount usually only reads image pull secrets from it, which needs no token. If the command prints nothing, skip to step 4.
Step 3: grant the token permission before you upgrade
The Flux object identity docs publish the ClusterRole to add. Commit it next to your Flux components, for example as clusters/my-cluster/flux-system/object-identity-rbac.yaml, and list it in the resources of that directory's kustomization.yaml.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: flux-object-identity
rules:
- apiGroups:
- ""
resources:
- serviceaccounts/token
verbs:
- create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: flux-object-identity
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: flux-object-identity
subjects:
- kind: ServiceAccount
name: source-controller
namespace: flux-system
- kind: ServiceAccount
name: kustomize-controller
namespace: flux-system
- kind: ServiceAccount
name: helm-controller
namespace: flux-system
- kind: ServiceAccount
name: notification-controller
namespace: flux-system
- kind: ServiceAccount
name: image-reflector-controller
namespace: flux-system
- kind: ServiceAccount
name: image-automation-controller
namespace: flux-system
Do this in its own commit, before the controllers change. On 2.9 the binding is redundant, so applying it changes nothing. When the 2.10 RBAC removes the old grant, this one is already in place and no source misses a beat. Once Flux has applied it, confirm the permission for one of the namespaces you found in step 2.
kubectl auth can-i create serviceaccounts --subresource=token \
--as=system:serviceaccount:flux-system:source-controller -n apps
The answer should be yes. Granting token creation cluster-wide is a real privilege, since a controller holding it can mint tokens for any ServiceAccount. If you only use object identity in a few namespaces, bind the ClusterRole with RoleBindings in those namespaces instead.
Step 4: keep BadgerDB if a rescan would hurt
If you scan many repositories on a rate-limited registry, you can keep the old backend for now and plan the switch for a quieter day. Patch the image-reflector-controller Deployment in the same kustomization.yaml that holds gotk-components.yaml.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- gotk-components.yaml
- gotk-sync.yaml
patches:
- target:
kind: Deployment
name: image-reflector-controller
patch: |
- op: add
path: /spec/template/spec/containers/0/args/-
value: --feature-gates=FluxStorage=false
The gate is opt-out from image-reflector-controller v1.3, which means it will eventually go away. Treat this as a postponement, not a decision.
Step 5: upgrade the controllers
Regenerate the components manifest with the 2.10 CLI and push it. Include the image controllers if you installed them, or flux install will leave them out and Flux will prune them.
git clone https://github.com/my-org/fleet-infra
cd fleet-infra
flux install \
--components-extra image-reflector-controller,image-automation-controller \
--export > ./clusters/my-cluster/flux-system/gotk-components.yaml
git add -A && git commit -m "Update Flux to $(flux -v)"
git push
flux reconcile ks flux-system --with-source
If you prefer the CLI path, rerun flux bootstrap with the same arguments you used the first time. It updates the same file in Git. Either way, flux check should report every controller healthy at its new version.
flux check
Step 6: migrate stored objects and Git manifests
The cluster now serves notification v1, but existing Alerts and Providers in etcd were written as v1beta3. flux migrate without -f rewrites every Flux object to its storage version and then sets each CRD's storedVersions to that single version. It needs cluster-admin.
flux migrate
kubectl get crd alerts.notification.toolkit.fluxcd.io -o jsonpath='{.status.storedVersions}'
Next, update the manifests in Git so they stop applying a deprecated API. With -f, flux migrate rewrites files in place. With --version=2.10 it moves Alert and Provider to v1, removes the fields v1 doesn't have and warns about each one with a hint about its replacement. Run it with --dry-run first and read the warnings.
cd fleet-infra
flux migrate -f . --version=2.10 --dry-run
flux migrate -f . --version=2.10
The warnings are your to-do list. A removed summary should move under eventMetadata, and a removed proxy should become a Secret referenced by proxySecretRef. Here is a migrated Alert that keeps its summary.
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Alert
metadata:
name: on-call
namespace: flux-system
spec:
providerRef:
name: slack
eventSeverity: error
eventSources:
- kind: Kustomization
name: "*"
eventMetadata:
summary: "Production cluster"
Commit, push and reconcile. Then trigger a failure in a test namespace and check that the alert still arrives with its summary.
What is worth trying after the upgrade
HelmRelease with an OCI chart template
A HelmRelease could already point at an OCIRepository through spec.chartRef, but you had to declare that OCIRepository yourself. In 2.10, spec.chart.kind accepts OCIRepository. helm-controller creates and manages the source in the release namespace, and the chart version and values change in one commit.
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: podinfo
namespace: default
spec:
interval: 10m
chart:
kind: OCIRepository
spec:
interval: 10m
url: oci://ghcr.io/stefanprodan/charts/podinfo
ref:
semver: ">= 6.0.0"
values:
replicaCount: 2
That atomic change is the point. With chartRef, bumping the chart and adapting the values happen in two objects, and an upgrade can run with new chart and old values in between.
Batched image updates
When CI pushes several images at once, image automation used to write one commit per update. The new spec.policyChangeDelay on ImageUpdateAutomation waits after the first ImagePolicy change, so updates that land within the delay go into one commit. The delay starts at the first change and later changes don't extend it.
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageUpdateAutomation
metadata:
name: apps
namespace: flux-system
spec:
interval: 30m
policyChangeDelay: 2m
sourceRef:
kind: GitRepository
name: flux-system
git:
checkout:
ref:
branch: main
commit:
author:
email: fluxcdbot@users.noreply.github.com
name: fluxcdbot
messageTemplate: |
Automated image update
{{ .Changed.Summary }}
push:
branch: main
update:
path: ./clusters/my-cluster
strategy: Setters
The commit template can now use .Changed.Summary, which writes one line per image update. If pushes get rejected because people commit to the same branch, the opt-in GitPushRetryOnFailure feature gate on image-automation-controller retries on top of the latest remote state.
Unsticking field ownership
kustomize-controller has a new --override-manager-before-dry-run flag. It takes field ownership back from the named managers before the server-side apply dry run. That fixes objects that a stale or foreign field manager has wedged, which used to need a hand edit of managedFields.
The order matters
Grant the token permission before the controllers change, so object identity never loses access. Upgrade the controllers before you migrate Git, because 2.9 CRDs don't serve notification v1 and kustomize-controller would fail to apply your new manifests. Run flux migrate on the cluster after the upgrade so etcd no longer holds v1beta3 objects when a later release stops serving that version.