I added a Horizontal Pod Autoscaler to a Flux-managed Deployment and watched the replica count bounce between what the HPA wanted and what Git said. Every few minutes, Flux would reconcile, see that spec.replicas had drifted from the Git state, and reset it. The HPA would immediately scale it back. Repeat forever.
This is a fundamental tension in GitOps: Flux’s job is to make the cluster match Git. The HPA’s job is to set the replica count based on metrics. They both write to the same field, and they disagree.
The problem Link to heading
Say your Deployment in Git has replicas: 3, and the HPA scales it to 7 based on CPU load. From Flux’s perspective, the cluster has drifted — spec.replicas is 7 but Git says 3. So Flux “fixes” it back to 3. The HPA sees CPU is still high, scales to 7 again. Next reconciliation, Flux resets it. You get a sawtooth pattern in your replica count that never stabilizes.
The fix: don’t declare replicas at all Link to heading
The Flux-native solution is to simply not set spec.replicas in the Deployment manifest:
# Before
spec:
replicas: 3
template:
...
# After — let HPA own it
spec:
template:
...
This works because Flux applies manifests using server-side apply. With server-side apply, every field has a manager — the controller that “owns” it. If your manifest doesn’t include spec.replicas, Flux never claims ownership of that field. The HPA writes to it, becomes the field manager, and Flux leaves it untouched on every reconciliation. No drift, no fight.
This is the idiomatic setup for Flux + HPA, and it’s what real-world clusters use. No special CRD configuration, no patches, no escape hatches — just don’t put a field in Git that you don’t actually want Git to own.
What about ignoreDifferences? Link to heading
If you’ve used Argo CD, you may know its ignoreDifferences feature, which tells the reconciler to skip specific fields during diff. Flux’s kustomize.toolkit.fluxcd.io/v1 Kustomization CRD does not have an ignoreDifferences field — it’s an Argo CD concept, not a Flux one. If you try to add it to a Flux Kustomization, the API server will reject the resource (or silently strip the unknown field, depending on validation).
The reason Flux doesn’t need it is exactly the server-side apply behavior described above: ownership is per-field, so the way to “ignore” a field is simply not to include it in the desired state.
When you can’t drop the field from Git Link to heading
Sometimes the field has to be in the rendered manifest — for example, an upstream Helm chart hardcodes replicas, or a base manifest you don’t control sets it. For those cases, Flux’s Kustomization spec has a patches field (which is in the schema) that strips fields from the rendered manifest before apply:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: my-app
namespace: flux-system
spec:
# ... other fields ...
patches:
- patch: |
- op: remove
path: /spec/replicas
target:
kind: Deployment
namespace: my-app
This is a strategic-merge / JSON6902 patch applied at render time. The rendered manifest goes out to the cluster without spec.replicas, so Flux never claims ownership of it, and the HPA owns it cleanly. Same end result as dropping the field from your base manifest, but useful when you can’t edit the source.
The clean setup Link to heading
For a Deployment that’s owned by an HPA:
- No
replicasfield in the base Deployment - No
replicasin any Kustomize overlay patches - If you can’t avoid it being rendered, strip it with
spec.patcheson the Flux Kustomization - HPA
minReplicasset to your desired floor
The HPA is the single source of truth for replica count, and Flux is the single source of truth for everything else.