Straightforward Kustomize Transformations: Customizing Kubernetes Resources Without Patches

0

Kustomize is a Kubernetes-native configuration management tool that lets us reuse plain YAML manifests and apply environment-specific changes on top of them. Instead of copying a full Deployment or Service manifest for every environment, we keep a shared base and create overlays for development, staging, production, or any other deployment variant.

A very important part of Kustomize is that not every change requires a patch. Some changes are common enough that Kustomize provides top-level transformer fields for them. These transformations are direct, readable, and usually easier to maintain than patch files.

In this article, we will focus on the straightforward transformations that can be configured directly in a kustomization.yaml file:

  • namespace
  • namePrefix
  • nameSuffix
  • labels
  • commonAnnotations
  • images
  • replicas

We will also discuss practical gotchas, including selector immutability, modern label syntax, image replacement behavior, and when a normal patch is still the better choice.

Why Direct Transformations Matter

When we manage Kubernetes manifests for multiple environments, we often need the same application structure with small differences.

For example:

EnvironmentNamespaceImage tagReplicasNaming
Developmentdev1.27.11dev-...-alpha
Stagingstaging1.27.1-rc2staging-...
Productionprod1.27.04prod-...

Without Kustomize, it is common to end up with duplicated YAML files. That creates maintenance overhead because every structural change has to be copied to multiple places.

Kustomize solves this by allowing us to keep the shared resources in a base and place environment-specific changes in overlays.

The base remains untouched. The overlay describes how the final rendered manifests should be changed.

The Base and Overlay Pattern

A typical Kustomize project looks like this:

nginx-app/
  base/
    deployment.yaml
    service.yaml
    kustomization.yaml
  overlays/
    dev/
      kustomization.yaml
    prod/
      kustomization.yaml

The base directory contains reusable Kubernetes resources. The overlays directory contains environment-specific configuration.

The base does not need to know about the overlays. This is important because the same base can be reused by multiple overlays.

Example Base Manifests

Let’s start with a small NGINX application.

Create the directory structure:

mkdir -p nginx-app/base nginx-app/overlays/dev nginx-app/overlays/prod

Create nginx-app/base/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app.kubernetes.io/name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.27.0
          ports:
            - containerPort: 80

Create nginx-app/base/service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app.kubernetes.io/name: nginx
spec:
  selector:
    app.kubernetes.io/name: nginx
  ports:
    - name: http
      port: 80
      targetPort: 80

Create nginx-app/base/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml

Now render the base:

kubectl kustomize nginx-app/base

This command only builds and prints the final YAML. It does not apply anything to the cluster.

A Development Overlay With Direct Transformations

Now let’s create a development overlay that changes the namespace, adds a name prefix and suffix, applies common metadata, changes the image tag, and sets the replica count.

Create nginx-app/overlays/dev/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

namespace: dev

namePrefix: dev-
nameSuffix: -alpha

labels:
  - includeSelectors: true
    pairs:
      app.kubernetes.io/part-of: ecommerce
      environment: dev
      tier: frontend

commonAnnotations:
  maintainer: finance@example.com
  repository: https://git.example.com/ecommerce/nginx-app

images:
  - name: nginx
    newTag: "1.27.1"

replicas:
  - name: nginx
    count: 1

Render the development overlay:

kubectl kustomize nginx-app/overlays/dev

The rendered output will include changes like these:

metadata:
  name: dev-nginx-alpha
  namespace: dev

The Deployment image will become:

image: nginx:1.27.1

The labels will be added to resource metadata, selectors, and pod templates because includeSelectors: true is enabled.

Transformation Overview

The following table summarizes the most useful direct transformations.

FieldExampleEffect
namespacenamespace: devAssigns namespaced resources to a specific namespace.
namePrefixnamePrefix: dev-Prepends a string to resource names.
nameSuffixnameSuffix: -alphaAppends a string to resource names.
labelslabels: [...]Adds labels to resources, and optionally to selectors and pod templates.
commonAnnotationscommonAnnotations: {...}Adds annotations to resources and pod templates.
imagesnewTag: "1.27.1"Rewrites container image names, tags, or digests.
replicascount: 3Sets the replica count for matching workload resources.

These transformations are especially useful for overlays because they describe environment-level changes clearly.

The namespace Transformer

The namespace field sets the namespace on namespaced resources.

namespace: dev

This is useful when the same base should be deployed to different namespaces:

overlays/
  dev/        -> namespace: dev
  staging/    -> namespace: staging
  prod/       -> namespace: prod

Important detail: setting namespace: dev does not automatically guarantee that the namespace exists in the cluster.

You have two common options:

  1. Create the namespace separately:
kubectl create namespace dev
  1. Add a Namespace manifest to the overlay:
apiVersion: v1
kind: Namespace
metadata:
  name: dev

Then include it in the overlay:

resources:
  - ../../base
  - namespace.yaml

For small labs, creating the namespace manually is fine. For GitOps-style environments, it is usually better to manage the namespace declaratively.

The namePrefix and nameSuffix Transformers

The namePrefix and nameSuffix fields modify resource names.

namePrefix: dev-
nameSuffix: -alpha

A base resource called nginx becomes:

dev-nginx-alpha

This is useful when you deploy multiple variants of the same application into the same namespace. For example, you might run a development and production-like version side by side:

dev-nginx-alpha
prod-nginx

Kustomize also updates many Kubernetes references automatically. For example, if a Deployment references a ConfigMap that is also managed by the same Kustomize build, Kustomize can update the reference to the transformed name.

This helps prevent manual find-and-replace mistakes.

When Name Prefixes and Suffixes Are Useful

Name transformations are useful when:

  • multiple overlays are deployed into the same namespace;
  • you want generated resources to be easy to identify;
  • your CI/CD pipeline creates temporary preview environments;
  • you want to distinguish alpha, beta, canary, or stable variants.

When to Be Careful

Be careful with very long prefixes and suffixes. Kubernetes resource names often have DNS-related length and character restrictions. Workload-generated resources, such as Pods created by Deployments, also derive their names from the parent resource name. Long names can become awkward quickly.

Use names that are clear but short.

Good examples:

namePrefix: dev-
nameSuffix: -v1

Avoid overly long names:

namePrefix: development-environment-for-finance-team-
nameSuffix: -experimental-alpha-release-candidate

Labels: Modern Syntax Instead of commonLabels

Older Kustomize examples often use commonLabels:

commonLabels:
  app: nginx
  environment: dev

In modern Kustomize, prefer the labels field because it gives more control over where labels are applied.

Example:

labels:
  - includeSelectors: true
    pairs:
      app.kubernetes.io/part-of: ecommerce
      environment: dev
      tier: frontend

With includeSelectors: true, Kustomize adds the labels not only to metadata.labels, but also to selectors and pod templates where appropriate.

For a Deployment, this can affect:

metadata:
  labels: ...
spec:
  selector:
    matchLabels: ...
spec:
  template:
    metadata:
      labels: ...

For a Service, this can affect:

metadata:
  labels: ...
spec:
  selector: ...

This is powerful, but it also means you must think carefully before adding labels to selectors.

Selector Labels Are Not Just Metadata

A label used in metadata.labels is metadata. A label used in a selector is part of how Kubernetes connects resources.

For example, a Service selects Pods using labels:

spec:
  selector:
    app.kubernetes.io/name: nginx

A Deployment also uses a selector to determine which Pods belong to it:

spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: nginx

This is why includeSelectors: true is powerful. It helps keep selectors and pod labels aligned.

But selector changes are not harmless.

For Deployments in apps/v1, the selector is immutable after the Deployment is created. If you deploy a resource first and later change the selector labels through Kustomize, Kubernetes can reject the update with an error similar to:

The Deployment "dev-nginx-alpha" is invalid: spec.selector: Invalid value: field is immutable

This commonly happens when you add a new labels transformer with includeSelectors: true to an overlay that was already applied.

Practical Rule

Use includeSelectors: true only for stable identity labels that you decide before the first deployment.

Good selector labels:

app.kubernetes.io/name: nginx
app.kubernetes.io/part-of: ecommerce
tier: frontend

Riskier selector labels:

version: "1.27.1"
commit: "a1b2c3d"
deployment-date: "2026-05-10"

Version and commit labels can be useful on pod templates, but they should usually not become part of stable selectors.

Applying Labels Without Changing Selectors

Sometimes you want labels on resources and pod templates, but not on selectors.

Use includeSelectors: false:

labels:
  - includeSelectors: false
    pairs:
      cost-center: finance
      owner: platform-team

Depending on your Kustomize version, you may also use includeTemplates to explicitly control whether labels are applied to pod templates.

Example:

labels:
  - includeSelectors: false
    includeTemplates: true
    pairs:
      commit: "a1b2c3d"
      release: "2026-05-10"

This pattern is useful for labels that help with observability, cost allocation, or traceability, but should not participate in resource selection.

The commonAnnotations Transformer

Annotations are also key-value metadata, but they are intended for non-identifying information. They are commonly used for tooling, automation, documentation, integrations, and operational hints.

Example:

commonAnnotations:
  maintainer: finance@example.com
  repository: https://git.example.com/ecommerce/nginx-app
  runbook: https://wiki.example.com/runbooks/nginx-app

Kustomize applies these annotations to resources and workload pod templates.

This is useful for:

  • maintainers and ownership;
  • repository links;
  • runbook links;
  • monitoring hints;
  • deployment metadata;
  • policy or governance tooling.

Unlike labels, annotations are not normally used by Kubernetes selectors. This makes them a better place for information that should not be used to group or select resources.

The images Transformer

The images transformer lets you change container images without writing a patch.

Base Deployment:

containers:
  - name: nginx
    image: nginx:1.27.0

Development overlay:

images:
  - name: nginx
    newTag: "1.27.1"

Rendered result:

image: nginx:1.27.1

You can also replace the image registry or repository:

images:
  - name: nginx
    newName: registry.example.com/platform/nginx
    newTag: "1.27.1"

Rendered result:

image: registry.example.com/platform/nginx:1.27.1

You can also use an image digest:

images:
  - name: nginx
    newName: registry.example.com/platform/nginx
    digest: sha256:1111111111111111111111111111111111111111111111111111111111111111

A digest is more immutable than a tag. In production, using image digests can make deployments more reproducible because the digest points to a specific image content hash.

Image Transformer Tips

Use the images transformer when:

  • dev and prod use different image tags;
  • CI/CD updates image tags during release;
  • production uses an internal registry mirror;
  • you want to pin an image by digest.

Avoid using latest in long-lived environments. It makes it harder to understand exactly what version is running.

The replicas Transformer

The replicas transformer lets each overlay set its desired replica count.

Development overlay:

replicas:
  - name: nginx
    count: 1

Production overlay:

replicas:
  - name: nginx
    count: 4

This is cleaner than patching spec.replicas in every environment.

Create nginx-app/overlays/prod/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

namespace: prod

namePrefix: prod-

labels:
  - includeSelectors: true
    pairs:
      app.kubernetes.io/part-of: ecommerce
      environment: prod
      tier: frontend

commonAnnotations:
  maintainer: finance@example.com
  repository: https://git.example.com/ecommerce/nginx-app

images:
  - name: nginx
    newTag: "1.27.0"

replicas:
  - name: nginx
    count: 4

Render it:

kubectl kustomize nginx-app/overlays/prod

The production Deployment will have:

replicas: 4

Replicas and Horizontal Pod Autoscaling

Be careful when a HorizontalPodAutoscaler manages the same workload. If an HPA is responsible for scaling, you generally should not keep fighting it by repeatedly applying a fixed spec.replicas value from Git.

A common approach is:

  • set fixed replicas in simple environments;
  • use HPA in environments where autoscaling is required;
  • avoid unnecessary manual changes to spec.replicas when HPA owns scaling behavior.

Preview, Apply, and Delete

Kustomize is easy to test because you can render the final YAML before applying it.

Preview the development overlay:

kubectl kustomize nginx-app/overlays/dev

Apply the development overlay:

kubectl apply -k nginx-app/overlays/dev

Delete the development overlay:

kubectl delete -k nginx-app/overlays/dev

Previewing first is a good habit. It allows you to inspect the final manifests and confirm that prefixes, suffixes, labels, annotations, images, and replicas are exactly what you expect.

Useful Inspection Commands

After applying an overlay, inspect the generated resources:

kubectl get deployment,service -n dev

Check labels:

kubectl get pods -n dev --show-labels

Check the rendered Deployment:

kubectl get deployment dev-nginx-alpha -n dev -o yaml

Check which image is running:

kubectl get deployment dev-nginx-alpha -n dev \
  -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'

Check replica count:

kubectl get deployment dev-nginx-alpha -n dev \
  -o jsonpath='{.spec.replicas}{"\n"}'

Direct Transformations vs Patches

Direct transformations are best for changes that are common, structured, and supported by Kustomize as first-class fields.

Use direct transformations for:

ChangeRecommended field
Put resources in a namespacenamespace
Add environment prefixes or suffixesnamePrefixnameSuffix
Add shared labelslabels
Add shared annotationscommonAnnotations
Change image tags or registriesimages
Change replica countreplicas

Use patches when you need targeted changes to specific fields that are not covered by direct transformers.

Good patch use cases:

  • change container resource requests and limits;
  • add or change environment variables;
  • add volumes and volume mounts;
  • change probes;
  • modify Service ports;
  • change Ingress hosts;
  • customize CRD-specific fields.

Example of a patch-worthy change:

patches:
  - target:
      kind: Deployment
      name: nginx
    patch: |-
      - op: add
        path: /spec/template/spec/containers/0/env
        value:
          - name: FEATURE_FLAG
            value: "true"

A good rule is simple: if Kustomize has a clear top-level transformer for the change, use it. If the change is resource-specific or deeply nested, use a patch.

Common Pitfalls

1. The Namespace Does Not Exist

This overlay:

namespace: dev

does not automatically create the dev namespace unless you also include a Namespace resource or create it separately.

2. Selector Labels Changed After Initial Deployment

If you add new selector labels to an existing Deployment, Kubernetes may reject the change because Deployment selectors are immutable.

Plan selector labels up front.

3. commonLabels in Older Examples

Many tutorials still show:

commonLabels:
  environment: dev

Prefer modern syntax:

labels:
  - includeSelectors: true
    pairs:
      environment: dev

The modern form is more explicit and gives better control over selectors and templates.

4. Image Names Do Not Match

If the images transformer does not update the image, check that the name matches the image used in the base manifest.

Base:

image: nginx:1.27.0

Overlay:

images:
  - name: nginx
    newTag: "1.27.1"

If your base uses a full registry path, match the image accordingly:

image: registry.example.com/platform/nginx:1.27.0

Overlay:

images:
  - name: registry.example.com/platform/nginx
    newTag: "1.27.1"

5. Too Much Logic in Overlays

Kustomize is not a programming language. It is not designed around conditionals, loops, or complex templating.

If your overlays become too clever, consider whether you need:

  • simpler overlays;
  • components;
  • a GitOps controller configuration;
  • Helm for chart-style parameterization;
  • a higher-level platform pipeline that renders manifests before Kustomize.

Recommended Overlay Style

A clean overlay should be easy to read.

Example:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

namespace: dev

namePrefix: dev-
nameSuffix: -alpha

labels:
  - includeSelectors: true
    pairs:
      app.kubernetes.io/part-of: ecommerce
      environment: dev
      tier: frontend

commonAnnotations:
  maintainer: finance@example.com
  repository: https://git.example.com/ecommerce/nginx-app

images:
  - name: nginx
    newTag: "1.27.1"

replicas:
  - name: nginx
    count: 1

This overlay tells a clear story:

  • deploy the base resources;
  • place them in the dev namespace;
  • rename resources with a dev-specific prefix and suffix;
  • add shared labels and annotations;
  • use a development image tag;
  • run one replica.

That is exactly what an overlay should do.

Practical Workflow

A practical Kustomize workflow looks like this:

# 1. Render and inspect
kubectl kustomize nginx-app/overlays/dev

# 2. Apply
kubectl apply -k nginx-app/overlays/dev

# 3. Verify
kubectl get all -n dev

# 4. Inspect final resource details
kubectl get deployment dev-nginx-alpha -n dev -o yaml

When changing an overlay, render the manifest before applying it:

kubectl kustomize nginx-app/overlays/dev > rendered-dev.yaml

Then inspect the file:

less rendered-dev.yaml

This is especially useful in code reviews because reviewers can see the final generated Kubernetes manifests.

Conclusion

Kustomize overlays are powerful because they allow us to reuse a shared base while applying environment-specific changes declaratively. Many of these changes do not require patches.

For common environment-level differences, use direct transformer fields:

  • use namespace to assign resources to a namespace;
  • use namePrefix and nameSuffix to distinguish resource names;
  • use labels to apply consistent metadata and, when appropriate, selectors;
  • use commonAnnotations for non-identifying operational metadata;
  • use images to update image names, tags, or digests;
  • use replicas to control workload size per environment.

The key is to keep the base clean and reusable, while keeping overlays clear, explicit, and small. When a change is cross-cutting and supported by a Kustomize transformer, use the transformer. When the change is resource-specific or deeply nested, use a patch.

That balance keeps Kubernetes configuration manageable without duplicating YAML across every environment.

References

  • Kubernetes: Declarative Management of Kubernetes Objects Using Kustomize - kubernetes[.]io/docs/tasks/manage-kubernetes-objects/kustomization/
  • Kubernetes: kubectl kustomize Reference - kubernetes[.]io/docs/reference/kubectl/generated/kubectl_kustomize/
  • Kubernetes: Labels and Selectors - kubernetes[.]io/docs/concepts/overview/working-with-objects/labels/
  • Kubernetes: Deployments - kubernetes[.]io/docs/concepts/workloads/controllers/deployment/
  • Kubernetes SIGs: Kustomize Repository - github[.]com/kubernetes-sigs/kustomize
  • Kubernetes SIGs: Kustomize Transformer Configurations - github[.]com/kubernetes-sigs/kustomize/blob/master/examples/transformerconfigs/README.md

Post a Comment

0 Comments

Post a Comment (0)

#buttons=(Ok, Go it!) #days=(20)

This site uses cookies from Google to deliver its services and analyze traffic. Your IP address and user-agent are shared with Google along with performance and security metrics to ensure quality of service, generate usage statistics, and to detect and address abuse. More Info
Ok, Go it!