Helm, Kustomize and GitOps with Argo CD · Kustomize Without the Mess

Patches That Delete What You Meant to Keep

A JSON patch replacing a list or a strategic merge patch with the wrong merge key silently drops configuration; check the rendered output.

Create a free account to save this page, take notes and track your progress. Sign in

Scenario

Example company: production needs one extra environment variable, REGION=eu-west-1. A teammate adds a JSON 6902 patch that "adds" it.

What you see in production

After the deploy, the API fails to start: DATABASE_URL is not set. The patch replaced the whole env list with a list containing only REGION.

The naive code

patches:
  - target: {kind: Deployment, name: api}
    patch: |
      - op: replace                       # replaces the entire list
        path: /spec/template/spec/containers/0/env
        value: [{name: REGION, value: eu-west-1}]

Reproduce it

The same intent written three ways, rendered with kubectl kustomize:

replace, add and strategic merge on an env list
mkdir -p base
cat > base/deployment.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata: {name: api}
spec:
  selector: {matchLabels: {app: api}}
  template:
    metadata: {labels: {app: api}}
    spec:
      containers:
        - name: api
          image: example/api:1.4.2
          env:
            - {name: DATABASE_URL, value: postgres://db/shop}
            - {name: LOG_LEVEL, value: info}
EOF
printf 'resources: [deployment.yaml]\n' > base/kustomization.yaml
render() {
  mkdir -p "o-$1"
  printf 'resources: [../base]\npatches:\n  - target: {kind: Deployment, name: api}\n    patch: |\n%s\n' "$2" > "o-$1/kustomization.yaml"
  echo "$1: $(kubectl kustomize o-$1 | grep -oE 'name: (DATABASE_URL|LOG_LEVEL|REGION)' | sed 's/name: //' | tr '\n' ' ')"
}
render replace '      - op: replace
        path: /spec/template/spec/containers/0/env
        value: [{name: REGION, value: eu-west-1}]'
render add '      - op: add
        path: /spec/template/spec/containers/0/env/-
        value: {name: REGION, value: eu-west-1}'
render strategic '      apiVersion: apps/v1
      kind: Deployment
      metadata: {name: api}
      spec:
        template:
          spec:
            containers:
              - name: api
                env: [{name: REGION, value: eu-west-1}]'

Output

replace: REGION 
add: DATABASE_URL LOG_LEVEL REGION 
strategic: REGION DATABASE_URL LOG_LEVEL 

Run this on your computer — see “Get Started” for setup.

Root cause

JSON 6902 patches are positional: replace on a list path replaces the whole list. Strategic merge patches merge lists of known Kubernetes types by a merge key (name for containers and env), but only for built-in types; for custom resources they also replace lists. Without looking at the rendered output, both mistakes go unnoticed.

The fix

  • To append to a list with JSON 6902, use op: add with path .../-.
  • Prefer strategic merge patches for built-in resources: env, containers, volumes merge by name.
  • For custom resources, use JSON patches with explicit indexes or restructure the base.
  • Render overlays in CI and assert critical settings exist (for example a policy that every API container has DATABASE_URL).

Trade-offs

  • JSON 6902 indexes break when the base list order changes.
  • Strategic merge semantics differ per field (some lists replace); check the API reference's patch strategy.

Detect it in production

  • CI assertions on rendered manifests for required environment variables and settings.
  • Startup failures right after configuration-only deploys.

Key points

  • JSON patch replace on a list replaces all of it; add with /- appends.
  • Strategic merge patches merge built-in lists by key; custom resources don't get this.
  • Always check rendered output and assert critical settings.

Further reading: Kustomize patches · Strategic merge patch

Practice questions

What does a JSON 6902 replace operation on an env list do?

Answer

It replaces the entire list with the given value, dropping every existing entry not in the patch.

How do you append one item to a list with a JSON patch?

Answer

Use op: add with the list path followed by /- (the end of the array).

Why are strategic merge patches safer for built-in resources?

Answer

They merge lists such as containers and env by their merge key (name), so adding one entry keeps the others; custom resources don't have merge keys, so lists are replaced.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?