skip to content

In Kubernetes, how do strategic merge patch, JSON merge patch and JSON patch differ, as chosen with `kubectl patch --type`?

level: middleimportance: nice to knowfreq 32%

answer

  1. three content types, one verb
  2. what happens to lists
  3. merge keys live in Go types
  4. operations with paths and indexes
  5. custom resources refuse one

basics

~20 s

JSON merge patch overlays a partial object and replaces any list wholesale. Strategic merge patch overlays one too, but merges lists such as containers by a key. JSON patch is an ordered list of operations on explicit paths.

solid answer

~40 s

`kubectl patch` defaults to `--type=strategic`. A **strategic merge patch** is a partial object, merged using per-field metadata from the built-in Go types. Lists like `containers` are merged by `name`, and some lists are atomic. A **JSON merge patch** (`--type=merge`, RFC 7386) is also a partial object, but any list you send replaces the live list entirely, and `null` deletes a key. A **JSON patch** (`--type=json`, RFC 6902) is a list of operations such as `add`, `remove`, `replace` and `test`, addressed by paths that can include list indexes. Custom resources do not accept strategic merge. The API server rejects it with 415, so you use merge or JSON patch there. The classic mistake: patching one container's image with `--type=merge` replaces the whole containers list and loses the sidecar.

code

bash · 3 lines
bash
kubectl patch deployment records-portal -p '{"spec":{"template":{"spec":{"containers":[{"name":"portal","image":"registry.example/records-portal:4.18.2"}]}}}}'

kubectl patch deployment records-portal --type=json -p '[{"op":"test","path":"/spec/template/spec/containers/0/name","value":"portal"},{"op":"replace","path":"/spec/template/spec/containers/0/image","value":"registry.example/records-portal:4.18.2"}]'

go deeper

for a junior

Know that kubectl patch changes part of an object, and that the default strategic type updates a container by name without touching the others.

for a middle

Explain how each format treats lists and deletions, and why custom resources need merge or JSON patch instead of strategic merge.

for a senior

Show you avoid index-based JSON patches without a test guard, and recognise a wiped sidecar as the result of a merge patch on a keyed list.

for a principal

Decide where imperative patches are acceptable in operations at all, and when a change must go through declarative apply so its ownership is recorded.

## Why there are several patch formats A **PATCH** request changes part of an object without sending all of it. The request's content type tells the API server how to read the body. Kubernetes accepts: - `application/strategic-merge-patch+json`: strategic merge patch (built-in kinds only). - `application/merge-patch+json`: JSON merge patch. - `application/json-patch+json`: JSON patch. - `application/apply-patch+yaml`: server-side apply, which is declarative and ownership-aware rather than a plain patch. `kubectl patch --type` picks one of the first three: `strategic` (the default), `merge` or `json`. ## JSON merge patch A **JSON merge patch** is a partial document laid over the object: 1. Maps are merged key by key. 2. A key set to `null` is deleted. 3. A **list is replaced as a whole** by the list in the patch. Rule 3 is the danger. The clinical-records portal's Deployment `records-portal` has two containers, `portal` and a log-shipping sidecar. A merge patch that sends only `containers: [{name: portal, image: ...:4.18.2}]` leaves the Pod template with just that one partial container. The sidecar is gone, and so are the `portal` container's ports, env and resources. ## Strategic merge patch A **strategic merge patch** looks like a merge patch, but the server consults metadata compiled into the built-in types: - Lists marked with a merge key are merged **by that key**. `containers`, `initContainers` and `env` are keyed by `name`, so the same body updates only `portal`'s image and keeps the sidecar. - Lists marked **atomic**, such as Pod `tolerations`, are still replaced whole. - Special directives such as `$patch: delete` inside a keyed list entry remove that entry. Because the metadata lives in the Go types, **custom resources cannot use it**. The CRD request handler accepts only JSON patch, merge patch and apply patch, and it rejects strategic merge with HTTP 415 Unsupported Media Type. ## JSON patch A **JSON patch** is not a document but a program: an ordered array of operations. | Op | Meaning | |---|---| | `add` | insert a value at a path (`-` appends to a list) | | `remove` | delete the value at a path | | `replace` | overwrite the value at a path | | `test` | fail the whole patch unless the path holds this value | | `move` / `copy` | relocate or duplicate a value | Paths are JSON Pointers such as `/spec/template/spec/containers/0/image`. List **indexes** make JSON patch precise but brittle: if someone reorders the containers, index 0 is a different container. `test` gives you a guard against that. ## Choosing | Need | Use | |---|---| | Quick edit of a built-in object | strategic (default) | | Any edit of a custom resource | merge, or json for list surgery | | Delete a map key | merge with `null`, or json `remove` | | Change list element N safely | json with a `test` op first | | Declarative, repeatable config | server-side apply, not a patch | Patches are imperative one-offs. Each patch type is also a **non-apply write**, so under server-side apply bookkeeping it is recorded as an `Update` by your field manager, and it takes ownership of the fields it changes. A later apply that disagrees then reports a conflict with that manager. ```bash kubectl patch deployment records-portal -p '{"spec":{"template":{"spec":{"containers":[{"name":"portal","image":"registry.example/records-portal:4.18.2"}]}}}}' kubectl patch deployment records-portal --type=merge -p '{"metadata":{"annotations":{"records.example/incident":null}}}' kubectl patch deployment records-portal --type=json -p '[{"op":"test","path":"/spec/template/spec/containers/0/name","value":"portal"},{"op":"replace","path":"/spec/template/spec/containers/0/image","value":"registry.example/records-portal:4.18.2"}]' ```

  • How do you remove one entry from a Deployment's `env` list with each patch type?
    With a strategic merge patch, send the entry by `name` together with `$patch: delete`, and the other variables are untouched. With a JSON patch, `remove` it by index, ideally after a `test` on that index's `name`. With a JSON merge patch you cannot target one entry: you must send the entire remaining `env` list, because the list is replaced wholesale.
  • When would you use server-side apply instead of any of these patches?
    When the change is part of desired configuration that will be re-applied, not a one-off. Apply is declarative and idempotent, merges lists using the schema's list types, works the same for custom resources, and records field ownership so disagreements surface as conflicts. Patches just write values and silently take fields from whoever set them.

saying these in an interview costs you the question

  • a JSON merge patch merges list items by name
  • strategic merge patch works on any custom resource
  • JSON patch paths cannot address list elements
  • setting a key to null in a merge patch stores null
  • kubectl patch defaults to a JSON merge patch