Sign in
← Kubernetes: Kustomize and Helm
6 min · easy · Lesson 2 of 9

Kustomize: transformers

namePrefix, labels, namespace, and image swaps: edits that cover the whole group of resources at once.

Why

Collecting manifests into a group doesn't get you far. Usually you need to apply the same edit to all of them: add a prefix to every name, attach a shared label, send everything to a different namespace, swap an image tag everywhere at once.

A transformer in Kustomize is a rule in kustomization.yaml that applies to every resource in the group at build time. The source manifests don't change by a single byte. The edit lives only in the Kustomize config and shows up in the rendered output of kubectl kustomize.

Types

FieldWhat it coversThe catch
namePrefix, nameSuffixthe names of all resourcesfixes references only in fields it knows about
namespacethe namespace of all resourcesoverrides what's written in the files
commonLabelsmetadata, selectors, the Pod templatean immutable-field error on a live Deployment
labelsmetadata, selectors, and the template, your choiceby default doesn't touch selectors
commonAnnotationsannotations on all resources and the Pod templateannotations have no selectors
imagesthe image's name, tag, or digestthe image string is matched literally

The order of fields in kustomization.yaml doesn't affect the result: Kustomize itself decides the order transformers apply in. The reliable way to check the result is a render, not reasoning: kubectl kustomize . shows the whole thing.

namePrefix and nameSuffix

namePrefix adds a string to the start of every resource's name, nameSuffix to the end.

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml
namePrefix: prod-

Deployment web becomes prod-web, Service web becomes prod-web. You need this when one app runs as several instances in the same namespace and the names must not collide.

Besides object names, Kustomize also rewrites the references between them in fields it knows about. If an Ingress referenced Service web, after the prefix the Ingress will have prod-web. Same with a ConfigMap in envFrom and in volumes:

envFrom:
  - configMapRef:
      name: prod-web-settings

namespace

The namespace field sets one namespace for all resources in the group:

namespace: demo

After that, kubectl apply -k . puts the objects into demo, even if the files themselves don't specify a namespace or specify a different one. That's handy for keeping manifests namespace-agnostic and setting the destination through config. The namespace itself still has to exist in the cluster or be included in the group as a separate resource.

Common labels and annotations

commonLabels attaches key-value pairs to every object in the group:

commonLabels:
  team: platform
  app.kubernetes.io/part-of: shop

This is where Kustomize's main trap hides. commonLabels writes the label into metadata.labels and, along with it, into the selectors: spec.selector on the Deployment, spec.selector on the Service, spec.template.metadata.labels on the Pod template. On a fresh resource everything goes fine. On a Deployment that already exists, the selector can't be changed, the field is immutable, and the API server rejects it with a field is immutable error.

Only the Deployment gets rejected. The Service's selector can change, and the same kubectl apply -k accepts the new pair for it. The running Pods don't have that label, the Service stops finding them, and traffic disappears even though the Pods are alive.

That's why current versions of Kustomize have deprecated commonLabels. kubectl warns about it on every build:

# Warning: 'commonLabels' is deprecated. Please use 'labels' instead. Run 'kustomize edit fix' to update your Kustomization automatically.

The replacement is the labels block, where the behavior is set explicitly:

labels:
  - pairs:
      team: platform
    includeSelectors: false

With includeSelectors: false the label only lands in objects' metadata.labels and leaves the selectors alone. That's the default value, but it's written explicitly so the reader doesn't have to guess. If you also need the label on the Pod, add includeTemplates: true: then it lands in the Pod template too, while the selectors stay as they were. includeSelectors: true behaves like commonLabels.

commonLabels still works and shows up in most other people's repositories, so it's worth knowing both forms.

commonAnnotations attaches annotations. It writes them into every object's metadata and into the Pod template. Annotations have no selectors, so immutable-field problems don't come up there.

Swapping the image

The images block changes a container's image without touching the Deployment:

images:
  - name: nginx
    newTag: 1.27.4-alpine

Kustomize finds containers whose image is named nginx and sets their tag to 1.27.4-alpine. Quote a tag that looks like a number: newTag: "1.25". Without quotes, YAML reads it as a number, and the build fails with cannot unmarshal number into Go struct field Image.images.newTag of type string.

You can also replace the image itself:

images:
  - name: nginx
    newName: registry.example.com/mirror/nginx
    newTag: 1.27.4-alpine
  - name: busybox
    digest: sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7

newName sets only the name, with no tag: don't append a tag there, or the build ends up with a string that has two tags. digest is used instead of a tag when you need to pin a specific image build. If you set both newTag and digest, Kustomize doesn't complain and writes both: nginx:1.27.4-alpine@sha256:.... Kubernetes pulls the image by digest from a string like that, the tag stays as a note for humans.

This is the most common transformer in real work. When rolling out a new version, CI edits a single line in kustomization.yaml instead of hunting for the tag across every manifest.

The match is against the image string exactly as it's written in the manifest. For docker.io/library/nginx:1.24, a rule with name: nginx silently does nothing, you need the full name: docker.io/library/nginx. The build passes with no errors or warnings.

What usually breaks

namePrefix fixes references only in fields Kustomize knows about. A Service name hardcoded as a string in an environment variable or in a ConfigMap's text keeps the old name:

env:
  - name: BACKEND_URL
    value: http://api

Spots like that are fixed with a patch or the replacements transformer (patches are the next lesson).

commonLabels on a live Deployment gives an immutable-selector error, and the Service in the same rollout silently loses its Pods. The fix is switching to labels with includeSelectors: false and running kubectl apply -k again. Recreating the Deployment also clears the error, but with downtime. Catching the problem before the rollout is easier with kubectl diff -k .: it runs the changes through the API server in dry-run mode and shows the rejection without changing anything.

An images entry matched nothing, and the cluster still runs the old tag. Kustomize doesn't warn about this. Before a rollout, it's worth checking the resulting images: kubectl kustomize . | grep 'image:'.

What you'll need for the tasks

Homework

Practice the theory on your own cluster: 1 step with a check. Set up the cluster with the Kubernetes locally track, the tasks live in the k8s-practice repository.

./run.sh start k8s-kustomize-transformers     # set up the starting state
./run.sh verify k8s-kustomize-transformers    # check the current step

In short

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 3 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson k8s-packaging-02-kustomize-transformers