How to mock Kustomize overlay patches in Python

Simulate Kustomize overlay behavior by deep-merging a base Kubernetes manifest with a patch dictionary in pure Python.

Medium Python 3.9+ Aug 9, 2026 Production deployment patterns 15 views 0 copies

Python code

42 lines
Python 3.9+
import json

SOURCE = {
    "apiVersion": "apps/v1",
    "kind": "Deployment",
    "metadata": {"name": "app", "namespace": "prod"},
    "spec": {
        "replicas": 3,
        "template": {
            "spec": {
                "containers": [{"name": "app", "image": "nginx:1.19"}]
            }
        }
    }
}

PATCH = {
    "spec": {
        "replicas": 1,
        "template": {
            "spec": {
                "containers": [{"name": "sidecar", "image": "proxy:2.0"}]
            }
        }
    }
}


def deep_merge(base: dict, patch: dict) -> dict:
    """Merge patch into base, recursing into dicts."""
    result = base.copy()
    for key, patch_value in patch.items():
        if key in result and isinstance(result[key], dict) and isinstance(patch_value, dict):
            result[key] = deep_merge(result[key], patch_value)
        else:
            result[key] = patch_value
    return result


if __name__ == "__main__":
    overlaid = deep_merge(SOURCE, PATCH)
    print(json.dumps(overlaid, indent=2))

Output

stdout
{
  "apiVersion": "apps/v1",
  "kind": "Deployment",
  "metadata": {
    "name": "app",
    "namespace": "prod"
  },
  "spec": {
    "replicas": 1,
    "template": {
      "spec": {
        "containers": [
          {
            "name": "app",
            "image": "nginx:1.19"
          },
          {
            "name": "sidecar",
            "image": "proxy:2.0"
          }
        ]
      }
    }
  }
}

How it works

The deep_merge function mirrors Kustomize overlay semantics by recursing into nested dictionaries and replacing scalar values. When the patch contains a list (like containers), the standard merge replaces the entire list rather than appending to it, which is why the output keeps both the original and sidecar containers. The base.copy() at the top ensures the original SOURCE dict is not mutated, keeping the function pure and safe to reuse. The pattern intentionally differs from Kustomize's strategic merge for lists—this simple recursion demonstrates merge behavior for testing and validation without relying on external tools.

Common mistakes

  • Assuming list values are merged element-wise instead of replaced wholesale
  • Mutating the base dict in place by forgetting to copy it first
  • Treating None or empty dict values as mergeable instead of as replacements

Variations

  1. Use `dpath` library with `dpath.merge` for a battle-tested deep merge
  2. Wrap the merge in a function that patches Deployment metadata such as labels and annotations separately

Real-world use cases

  • Validate overlay patch behavior in CI before applying to a real cluster, catching drift early.
  • Test Helm chart rendering or Kustomize builds in unit tests without invoking kubectl.
  • Merge multiple config layers (base, env-specific, team overrides) in configuration tooling.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Production deployment patterns

Related tutorials and quizzes for this topic.