Sign in
← Kubernetes: Kustomize and Helm
6 min · easy · Lesson 3 of 9

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

containers: merge key name base - name: app image: nginx:1.26.2 - name: log + patch - name: app image: nginx:1.27.3 → result - name: app image: nginx:1.27.3 - name: log args: no merge key base - --log=info - --port=80 + patch - --log=debug → result - --log=debug --port=80 is gone
Merging happens by the name key; a string list has no key, and the patch overwrites it wholesale.

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

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

Done reading?

Mark the lesson as read to move your track progress forward.

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.

Get access for $49

Tell the agent: use MCP izi, give me lesson k8s-packaging-03-kustomize-patches