Kustomize: patches
A targeted edit to a single resource: strategic merge patch, the JSON 6902 format, and how to pick a target.
Why
A transformer hits every resource in a group. The prefix lands on every name, the label on every object. Sometimes that's too much: replicas need raising on one Deployment, a memory limit needs fixing on one container, and nothing else should change.
A patch is an edit addressed to a specific resource. Kustomize takes the
built object and applies a change described in one of two formats:
strategic merge patch or JSON 6902. Both are written in the patches
block of kustomization.yaml, and the source manifests stay unchanged.
Strategic merge patch
The most readable option. You write a trimmed-down piece of the same
manifest: just the field you're changing, plus the minimum needed to
identify the object (apiVersion, kind, metadata.name).
# kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
patches:
- target:
kind: Deployment
name: web
patch: |
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
The source deployment.yaml doesn't change, and in the output of
kubectl kustomize ., replicas becomes 3. The target block says
which object to apply the fix to.
The word strategic in the name means Kustomize knows the schema of
built-in Kubernetes types and merges lists by a key instead of replacing
them wholesale. For the container list, the merge key is name. So a
patch like this fixes one container's resources and doesn't drop the
rest:
patches:
- target:
kind: Deployment
name: web
patch: |
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
template:
spec:
containers:
- name: app
resources:
limits:
memory: 512Mi
The container is found by name: app. Dictionaries are merged too: if
app already had limits.cpu, it stays alongside the new memory. If
no container with that name exists in the Deployment, Kustomize won't
report an error. It adds a new container that has only name and
resources, with no image. The build passes with not a single warning,
so always check the container name in the patch against the manifest.
Not every list has a merge key. Containers have one, but string lists
(command, args) don't: a patch replaces that kind of list wholesale.
A strategic merge won't let you append one argument to the existing
ones; you need JSON 6902 with add and a path ending in -.
A patch in a separate file
Inline is convenient for two lines. When the edit grows, pull it out:
patches:
- path: patches/resources.yaml
target:
kind: Deployment
name: web
The file holds the same trimmed-down manifest. If it specifies
apiVersion, kind and metadata.name, you can skip the target
block: Kustomize finds the target by those fields. This works for an
inline patch too.
JSON 6902
The second format describes not the result but the operation: what to do, where, and with what value. The name comes from RFC 6902, the JSON Patch standard.
patches:
- target:
kind: Deployment
name: web
patch: |
- op: replace
path: /spec/replicas
value: 3
- op: add
path: /spec/template/spec/containers/0/args/-
value: --log=debug
Available operations are add, remove, replace, move, copy and
test. The path is written as a JSON Pointer: segments separated by
slashes, list items by numeric index. containers/0 here is the first
container in the list.
The - character in place of an index means the end of the list, that
is, appending. It's the only way to append an item without knowing the
current length. The list itself must already exist: if the container
has no args, the path .../args/- won't work, and then you add the
whole list at once (path: .../args, value as a list).
For JSON 6902, the target block is required: the operations themselves
have neither kind nor an object name.
Strategic merge is easier to read and fits most edits. JSON 6902 is irreplaceable where you need to address a list item by position, append an item to a string list, remove a specific item, or fix an object with no merge schema known to Kustomize, such as a Custom Resource.
How to pick a target
target can do more than kind and name:
patches:
- path: patches/probe.yaml
target:
kind: Deployment
labelSelector: "tier=backend"
Besides labelSelector there's annotationSelector, namespace,
group, version. The name field understands a regular expression,
so name: "web-.*" covers several objects with one patch. The expression
must match the name in full: name: we won't touch Deployment web.
What usually breaks
The path in JSON 6902 isn't found. Both add and replace need the
parent of the last segment to already be in the object. If the container
has no resources, a patch on .../resources/limits/memory fails:
error: add operation does not apply: doc is missing path: "/spec/template/spec/containers/1/resources/limits/memory": missing value
Add the missing level whole (path: .../resources, value as a
dictionary), or make that edit through strategic merge, which creates
intermediate fields itself. remove on a path that doesn't exist also
errors.
A patch that matched nothing. If target is set and no object in the
group has that kind, name or labels, Kustomize silently skips the
patch: no error, the build passes, no fix applied. Most often it's a
typo or a stale name in target. The easiest way to check is rendering:
kubectl kustomize . shows the objects after every fix is applied, and
you'll see whether yours landed or not. A strategic merge patch with no
target and a wrong metadata.name, on the other hand, fails right
away:
error: no resource matches strategic merge patch "Deployment.v1.apps/web-api.[noNs]": no matches for Id Deployment.v1.apps/web-api.[noNs]; failed to find unique target for patch Deployment.v1.apps/web-api.[noNs]
A string list is gone. A strategic merge with args: ["--log=debug"]
leaves the container with exactly one argument; the rest disappear with
no warning. Check args and command in the build output after every
such patch.
Deleting through strategic merge. Simply dropping a line from the patch
isn't enough: a patch only describes what's added or changed. A field is
removed with the value null (resources: null), and a list item with a
merge key by the $patch: delete directive:
spec:
template:
spec:
containers:
- name: log
$patch: delete
What you'll need for the tasks
- Lab 01 (Build): raise replicas and the memory limit on one Deployment with a patch so that the second Deployment and the neighboring container stay unchanged.
- Lab 02 (Fix): figure out why one patch doesn't apply while another breaks a container's args, and fix both.
- Commands:
kubectl kustomize <folder>,grepover the output,kubectl version --client. No cluster or Docker needed.
Homework
Practice the theory on your own cluster: 1 step with a check. Set up the cluster with the Kubernetes locally track, the tasks live in the k8s-practice repository.
./run.sh start k8s-kustomize-patches # set up the starting state
./run.sh verify k8s-kustomize-patches # check the current step
In short
- A patch fixes a specific resource, a transformer fixes the whole group.
- A strategic merge patch is a trimmed-down manifest with the fields that identify the object plus the field you're changing.
- JSON 6902 describes
add,remove,replaceoperations by JSON Pointer; a-at the end of the path means appending to an existing list. targetpicks the object bykind,name(including by regex),labelSelectorandannotationSelector. A patch that matched nothing is skipped silently.- Container lists merge by
name, while string lists likeargsare replaced wholesale.
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson k8s-packaging-03-kustomize-patches
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress