| title | Cannot Patch Immutable Field | |||||
|---|---|---|---|---|---|---|
| error_message | cannot patch "...": field is immutable | |||||
| category | helm | |||||
| severity | High | |||||
| recovery_time | 10–30 min | |||||
| k8s_versions | 1.20+ | |||||
| tags |
|
|||||
| related |
|
Severity: High · Typical recovery time: 10–30 min · Affected versions: 1.20+
Error: UPGRADE FAILED: cannot patch "web" with kind Deployment:
Deployment.apps "web" is invalid: spec.selector: Invalid value:
... field is immutable
Some Kubernetes object fields cannot be changed after creation. When a Helm
upgrade renders a manifest whose immutable field differs from the live object,
the API server rejects the patch and Helm reports field is immutable. Helm
cannot resolve this by patching — the only path forward is to delete and
recreate the object (or revert the value).
Common immutable fields include spec.selector on Deployments/StatefulSets,
spec.template on Jobs, the volumeClaimTemplates of a StatefulSet, a
Service's spec.clusterIP, and most fields on a PVC except a size increase.
These changes usually slip in when chart label/selector templating changes
(for example a chart upgrade that alters matchLabels).
Applies to all supported releases (1.20+). The set of immutable fields is defined by each resource's API validation and is stable, though newer charts that change default label sets are a frequent trigger after an upstream bump.
- A chart upgrade changed
spec.selector.matchLabelson a Deployment/StatefulSet - Modifying a Job's pod template (Jobs are effectively immutable once created)
- Changing a StatefulSet's
volumeClaimTemplatesorserviceName - Setting/altering a Service
clusterIPor a PVC'sstorageClassName
flowchart TD
A[field is immutable] --> B[Identify object + field from message]
B --> C{Did the value need to change?}
C -- No, accidental --> D[Restore prior value in chart/values]
C -- Yes, intentional --> E[Plan delete + recreate]
E --> F{Stateful data attached?}
F -- Yes --> G[Back up PVCs first]
F -- No --> H[Delete object, re-upgrade]
Read which object and field the message names, then compare the rendered value against the live object to confirm whether the change was intentional.
helm get manifest my-release -n my-namespace | grep -A20 'kind: Deployment'
kubectl get deployment web -n my-namespace -o jsonpath='{.spec.selector}'
kubectl describe deployment web -n my-namespace
helm history my-release -n my-namespace
helm diff upgrade my-release ./chart -n my-namespace# live selector
{"matchLabels":{"app":"web"}}
# rendered (new) selector after chart bump
{"matchLabels":{"app.kubernetes.io/name":"web"}}
- If the change was accidental (chart relabel), pin the old selector/labels in
values.yamlso the rendered object matches the live one. - If the change is intentional, delete and recreate the affected object.
- For Services, avoid setting
clusterIP; let it default so it stays stable.
- Revert the value in the chart/values and
helm upgrade my-release ./chart -n my-namespace --atomic— Blast radius: none beyond a normal reconcile; preferred when the change was unintended. - Delete and recreate (intentional change):
kubectl delete deployment web -n my-namespacethen re-run the upgrade. Blast radius: the workload is down until recreated — drain traffic first. - For a StatefulSet selector change,
kubectl delete statefulset web -n my-namespace --cascade=orphanthen re-upgrade. Blast radius: orphaning keeps pods/PVCs running while the controller is recreated; verify data safety before deleting the StatefulSet object.
helm upgrade completes with deployed, kubectl get deployment web shows the
expected selector, and kubectl rollout status reports the workload ready.
- Treat selectors/labels as immutable contracts; never change them casually.
- Review chart upgrade notes for label/selector changes before bumping.
- Gate upgrades with
helm diff upgradein CI to catch immutable diffs early.