Kustomize’s strategic merge patch is surprisingly adept at overriding environment variables in a deployment, but only if you understand how it resolves conflicts.

Let’s say you have a base deployment like this:

# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: app-container
        image: my-repo/my-app:v1.0.0
        env:
        - name: LOG_LEVEL
          value: "INFO"
        - name: FEATURE_FLAG_X
          value: "false"

And you want to override LOG_LEVEL and add a new variable API_KEY using a Kustomize patch. Your kustomization.yaml might look like this:

# kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- base/deployment.yaml

patchesStrategicMerge:
- patch.yaml

And your patch.yaml:

# patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: app-container
        env:
        - name: LOG_LEVEL
          value: "DEBUG"
        - name: API_KEY
          value: "supersecret123"

When you run kustomize build ., you’ll see the LOG_LEVEL is indeed overridden, and API_KEY is added.

$ kustomize build .
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - env:
        - name: LOG_LEVEL
          value: DEBUG
        - name: API_KEY
          value: supersecret123
        image: my-repo/my-app:v1.0.0
        name: app-container

The magic happens because Kustomize’s strategic merge patch understands Kubernetes object structures. For lists like env, it doesn’t just append. It looks for elements with matching keys (in this case, the name field of an environment variable) and merges them. If an element with a matching key exists in both the base and the patch, the patched version replaces the base version. If an element only exists in the patch, it’s added.

The containers list is also handled strategically. Kustomize finds the container named app-container in the base and applies the patch’s container definition to it. This means any fields defined in the patch for app-container will override or augment the base.

Let’s dive deeper. Imagine your base deployment has multiple containers, and you only want to patch one.

# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: app-container
        image: my-repo/my-app:v1.0.0
        env:
        - name: LOG_LEVEL
          value: "INFO"
      - name: sidecar-container
        image: my-repo/sidecar:v1.0.0
        env:
        - name: SIDEBAR_URL
          value: "http://localhost"

And your patch only targets app-container:

# patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: app-container
        env:
        - name: LOG_LEVEL
          value: "DEBUG"

Kustomize will correctly identify app-container and update its LOG_LEVEL. Crucially, sidecar-container and its SIDEBAR_URL environment variable remain untouched because the patch didn’t specify anything for it. This is the power of strategic merging – it’s intelligent about how it combines lists and maps based on defined keys.

What if you want to remove an environment variable? Strategic merge patch doesn’t have a direct "delete" operation. You achieve this by setting the value to null or by using a different patching strategy like JSON patch. However, for simple overrides and additions, strategic merge is usually sufficient and more readable.

If your patch definition for an environment variable doesn’t include a name field, Kustomize will treat it as a new variable to be appended. This is why specifying name is critical for overriding existing variables.

Consider a scenario where you have multiple patches. Kustomize applies them sequentially in the order they appear in the patchesStrategicMerge list. If two patches try to override the same environment variable, the last patch in the list wins.

This behavior is essential for managing environment variations. You can have a base deployment, a development overlay that patches LOG_LEVEL to DEBUG and sets API_URL to a dev endpoint, and a production overlay that sets LOG_LEVEL to WARN and API_URL to a production endpoint.

# overlays/development/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base

patchesStrategicMerge:
- deployment-patch.yaml

# overlays/development/deployment-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: app-container
        env:
        - name: LOG_LEVEL
          value: "DEBUG"
        - name: API_URL
          value: "http://dev.api.example.com"

The underlying mechanism Kustomize uses for strategic merge is based on the patchMergeKey defined for Kubernetes list elements. For EnvVar within a Container, the patchMergeKey is name. This tells Kustomize how to uniquely identify and match elements within the env list.

The most common pitfall is forgetting to specify the name field in your patch’s environment variable definition. Without it, Kustomize will simply append a new, unrelated environment variable, rather than overriding an existing one.

The next hurdle you’ll likely encounter is dealing with more complex patching scenarios, like modifying nested fields within an existing environment variable or conditionally applying patches based on external factors.

Want structured learning?

Take the full Kustomize course →