KubernetesVII · Declarative Resource ManagementDeclarative resource management
kubectl patch — strategic merge, JSON patch, JSON merge patch
What you'll learn
- Use kubectl patch with strategic merge, JSON patch, and JSON merge patch
- Identify when each format is the right choice
- Combine patch with strategic merge patch directives (retainKeys, $patch: delete)
- Recognise when patch is the right tool vs apply or edit
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
kubectl patch writes a partial object to the API server.
It is the right tool when you want to change one field without
disturbing the rest. This lesson covers the three patch formats
kubectl supports, how each behaves on lists and maps, and
when patch is the right tool versus apply or edit.
The three patch formats
# Substitute your own values before running:
KIND=deployment
NAME=web
kubectl patch "$KIND/$NAME" --type=strategic # default
kubectl patch "$KIND/$NAME" --type=json # RFC 6902
kubectl patch "$KIND/$NAME" --type=merge # RFC 7396
If you omit --type, kubectl uses strategic merge patch.
The other two are explicit.
Strategic merge patch — the default
Strategic merge patch is field-aware: it knows that a list of
containers is merged element-by-element (keyed by name), and
that a list of nodeSelectorTerms is replaced wholesale (no
merge key).
# Add a label
kubectl patch deployment web -p '{"metadata":{"labels":{"env":"prod"}}}'
# Change the image
kubectl patch deployment web -p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx","image":"nginx:1.27.2"}]}}}}'
# Add a sidecar container
kubectl patch deployment web --type=strategic -p '
spec:
template:
spec:
containers:
- name: sidecar
image: envoy:1.30
'
The behaviour:
- Maps (objects) are merged recursively. A field in the patch overrides the same field on the live object; a field absent from the patch is left alone.
- Lists with a merge key (e.g., containers keyed by
name, volumes keyed byname) are merged element by element. A new element is added; an existing element with the same key is updated; an element absent from the patch is left alone. - Lists without a merge key (e.g.,
tolerations,nodeSelectorTerms) are replaced wholesale. A patch that includes the list replaces the entire list.
Strategic merge patch directives extend the syntax:
# Delete a list element by key (containers)
- op: remove
path: /spec/template/spec/containers
value:
$patch: delete
# Replace a list element wholesale
- op: replace
path: /spec/template/spec/containers
value:
- name: nginx
image: nginx:1.27.2
# Retain unknown keys when replacing
spec:
template:
spec:
containers:
- name: nginx
image: nginx:1.27.2
$retainKeys:
- name
The $patch: delete directive tells strategic merge to
remove the named list element. Without it, removing a
container from a list would replace the entire list (and
remove all other containers).
JSON patch — RFC 6902
JSON patch is a sequence of operations: add, remove, replace, move, copy, test. It is the most precise patch format; you name the path explicitly and say exactly what to do.
# Replace the image
kubectl patch deployment web --type=json -p '[
{"op":"replace","path":"/spec/template/spec/containers/0/image","value":"nginx:1.27.2"}
]'
# Remove the second container
kubectl patch deployment web --type=json -p '[
{"op":"remove","path":"/spec/template/spec/containers/1"}
]'
# Add a new label
kubectl patch deployment web --type=json -p '[
{"op":"add","path":"/metadata/labels/env","value":"prod"}
]'
# Test-and-replace (atomic)
kubectl patch deployment web --type=json -p '[
{"op":"test","path":"/spec/replicas","value":3},
{"op":"replace","path":"/spec/replicas","value":5}
]'
The test operation is the killer feature: it asserts the
current value matches the expected value, and the patch fails
otherwise. This makes optimistic-concurrency patches safe —
the patch either applies the change against the expected
pre-state, or fails without modifying anything.
JSON patch is the right format when:
- You need atomic test-and-modify semantics.
- You are removing an element from a list without a merge key.
- You are modifying a specific position in a list (e.g.,
containers[1]). - The patch logic is generated programmatically and needs explicit operations.
JSON patch is the wrong format when:
- You want to merge a partial object declaratively (use strategic merge).
- The list has a merge key and you want natural merging (strategic merge is cleaner).
JSON merge patch — RFC 7396
JSON merge patch is the simplest format: a partial object
that is merged with the live state. Field present in patch
overrides; field absent is left alone; field with value
null is deleted.
# Add/update fields
kubectl patch deployment web --type=merge -p '{
"spec":{"replicas":5}
}'
# Delete a field
kubectl patch deployment web --type=merge -p '{
"spec":{"replicas":null}
}'
The behaviour:
- A field with a non-null value in the patch overrides the live value.
- A field with a value of
nulldeletes the field from the live object. - A field absent from the patch is left alone.
- Lists are replaced wholesale. JSON merge patch has no merge key; any list in the patch replaces the entire list on the live object.
JSON merge patch is the simplest format but the most limiting. Lists cannot be partially updated; the entire list must be included in the patch. For most Kubernetes use cases, strategic merge or JSON patch is the better choice.
Choosing the right patch type
| Use case | Format |
|---|---|
| Add/update fields, merge with live state | strategic |
| Remove a field | strategic with $patch: delete |
| Replace a list element by index | json |
| Atomic test-and-modify | json with test op |
| Remove a list element by key | strategic with $patch: delete |
| Replace the entire object | json with replace |
| Simple update with no list manipulation | merge |
Patch vs apply vs edit
Three commands write partial changes:
kubectl patch deployment web -p '{"spec":{"replicas":5}}' # one field
kubectl edit deployment web # interactive
kubectl apply -f deployment.yaml # manifest-driven
- patch is for one-off, targeted changes that should not touch the rest of the object. It does not update last-applied-configuration.
- edit is for interactive changes where you want to see and edit the whole object.
- apply is for manifest-driven changes where the manifest is the source of truth.
patch does not update last-applied-configuration. This
means a subsequent apply of the manifest may overwrite the
patch’s change. The pattern:
- Use
patchfor one-off triage. - If the change is sustained, reflect it in the manifest.
applythe manifest; the patch is preserved (becauseapply’s merge sees the patch as a manual change that wasn’t in last-applied).
Production patterns
Adding a label to a Deployment without a rollout:
kubectl patch deployment web --type=strategic -p '
metadata:
labels:
canary: "true"
'
Labels are part of metadata, not spec.template.metadata.
Adding a label to the Deployment itself does not trigger a
rollout. Adding a label to the Pod template does.
Triggering a rollout via a patch:
kubectl patch deployment web -p '
spec:
template:
metadata:
annotations:
kubectl.kubernetes.io/restartedAt: "2026-08-16T12:00:00Z"
'
The annotation change is a change to the Pod template’s metadata, which the Deployment controller detects as a template change and rolls out a new ReplicaSet.
Removing a container by name:
kubectl patch deployment web --type=strategic -p '
spec:
template:
spec:
containers:
- name: sidecar
$patch: delete
'
The $patch: delete directive removes the named container
without affecting the others. Without it, the patch would
replace the entire containers list with an empty list,
removing all containers.
Cross-course references
- The Terraform course part
XVII-Terraform-Driftcovers state-vs-config drift; patch is the kubectl equivalent of a Terraform update operation. - The Ansible course part
XXV-Ansible-CheckDiffcovers surgical changes; patch is the same idea at the cluster level. - The Linux course part
XXII-Linux-NetTroubleshootcovers surgical network changes; the patch discipline is the same.
Quiz
Knowledge check · 4 questions
Q1. Which patch format is the default for kubectl patch when --type is not specified?
Q2. `kubectl patch` updates the last-applied-configuration annotation, so a subsequent `kubectl apply` from the same manifest preserves the patch's change.
Q3. An operator wants to remove a sidecar container from a 3-container Deployment using kubectl patch. They try `kubectl patch deployment web -p '{"spec":{"template":{"spec":{"containers":[{"name":"sidecar","image":"envoy:1.30"}]}}}}'`. Diagnose what happens.
Deployment has three containers: - name: nginx (main) - name: sidecar - name: metrics-exporter The operator wants to remove the sidecar. Patch attempted: ```bash kubectl patch deployment web -p ' {"spec":{"template":{"spec":{"containers":[{"name":"sidecar","image":"envoy:1.30"}]}}}} ' ```
Q4. When is JSON patch the right format instead of strategic merge patch?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Use strategic merge patch for declarative merges. Strategic merge is the Kubernetes-native format; it understands field-level merge semantics.
- Use JSON patch for test-and-modify. The
testoperation makes optimistic-concurrency patches safe. - Use
$patch: deleteto remove list elements by key. Without it, strategic merge patch replaces the whole list. - Reflect patches in the manifest.
patchdoes not update last-applied-configuration; a subsequent apply will revert the patch. - Capture the object before every patch. A failed patch that left the object in an unexpected state is recoverable only if you have the pre-patch YAML.