Alright, let’s get our hands dirty with the kustomization.yaml file. This is your single source of truth for a Kustomize overlay, the control panel from which you direct the entire symphony of YAML mangling. Forget templates; we’re layering and patching like a digital archaeologist, carefully brushing away the generic to reveal the environment-specific.

The file is essentially a manifest of manifests. It tells Kustomize: “Here are the raw materials, here’s how I want you to change them, now go build me something I can actually kubectl apply.”

The resources field: Your base camp

This is non-negotiable. The resources array is your list of what Kustomize should even look at. These can be paths to other Kustomize directories (which is how you build those famous overlays) or direct paths to raw YAML files. Think of it as your ingredients list. You can’t make a cake if you don’t first declare you need flour, eggs, and sugar.

# kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  # Point to a base directory that has its OWN kustomization.yaml
  - ../../base
  # Or just slam in a specific file directly. Kustomize isn't picky.
  - ./my-extra-service.yaml
  # You can even use a URL, because why not live dangerously?
  # - https://github.com/kubernetes/website/blob/main/content/en/examples/pod.yaml

Why it works this way: Kustomize is declarative. You don’t tell it how to find files; you declare which files constitute your starting point. This makes your overlay perfectly reproducible. A common pitfall? Forgetting that paths are relative to the kustomization.yaml file itself, not where you’re running the kustomize build command from. Get the path wrong, and Kustomize will happily build you a beautiful, empty manifest.

The patches field: Performing surgery

This is where Kustomize gets its superpowers. While resources is your “what,” patches are your “how to change it.” You have two main ways to do this, and the distinction is crucial.

Strategic Merge Patches (the sane default): These are the patches you’re most likely to use. You give Kustomize a snippet of YAML that looks exactly like the original resource, but with only the fields you want to change or add. Kustomize uses the apiVersion, kind, name, and namespace to find the right target and merges your changes in. It’s intuitive because you’re basically writing the diff.

# kustomization.yaml
patches:
  - target:
      kind: Deployment
      name: my-app
    patch: |-
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: my-app
      spec:
        replicas: 3 # Change the replica count
        template:
          spec:
            containers:
            - name: server
              resources: # Add a resources block that wasn't there before
                requests:
                  memory: "64Mi"
                  cpu: "250m"

JSON Patch (the scalpel): For when a strategic merge isn’t precise enough. JSON Patch uses a JSONPath-like syntax to explicitly define an operation (add, remove, replace, etc.) on a specific path. It’s more powerful but also more brittle and harder to read. You break this out when you need to modify a single element in a large array or change a key deep within a complex structure.

# kustomization.yaml
patches:
  - path: ./json-patch.yaml
    target:
      kind: Deployment
      name: my-app

# ./json-patch.yaml
- op: replace
  path: /spec/template/spec/containers/0/image
  value: my-app:2.1.0-hotfix # Change the exact image tag for the first container

The designers’ questionable choice: The syntax for inline vs. external patches is inconsistent. For strategic merge, you use patch: | for inline and path: for a file. For JSON Patch, you must use an external file (path:). It’s a minor annoyance that trips everyone up. Just accept it.

The images field: The easy way

You could use a patch to change an image tag. It would work. But it would be like using a rocket launcher to open a jar of pickles. The images field is a specialized, declarative shortcut for the one thing we change more than anything else: container images.

# kustomization.yaml
images:
  - name: my-app # The image name used in your Pod templates
    newName: my-registry.com/my-team/my-app # Optional: change the whole repo
    newTag: v2.1.0 # Change just the tag
  - name: nginx # Find *any* container using an image named 'nginx'
    newTag: 1.25.3-alpine # And update its tag

Why this is brilliant: It’s intention-revealing. Anyone looking at your kustomization.yaml immediately knows, “Ah, we’re pinning to version v2.1.0 here.” It’s also far less error-prone than writing a patch, especially if the same image is used in multiple places. The best practice is to always use this over a patch for simple image updates.

The namePrefix field: Avoiding YAML-collision headaches

This one is deceptively simple but absolutely critical for multi-tenant or multi-environment setups. namePrefix prepends a string to the names of almost all generated resources. I say “almost all” because some things, like ServiceAccount names referenced by a Pod spec, are also updated by Kustomize’s magic to keep things consistent. It’s smart.

# kustomization.yaml
namePrefix: prod-

If your base deployment creates a Deployment named my-app, applying this overlay will generate a Deployment named prod-my-app. This is how you stop your dev, staging, and prod deployments from all having the same name and trying to fight each other for control of the cluster. It’s a namespace inside a namespace. The pitfall? It doesn’t change the name field inside container specs, like environment variables referencing a ConfigMap. For that, you’ll need a patches field or the more powerful nameSuffix and namespace fields. But for most purposes, namePrefix is your first line of defense against naming chaos.