Kustomize: Kubernetes Bases, Overlays and Patches

Use Kubernetes Kustomize bases and overlays to patch manifests, change images and replicas, generate configuration, preview changes, and apply with kubectl.

Published

Updated

Read time 12 min read

Reviewed byDeepak Prasad

Kubernetes Kustomize bases and overlays with kustomization.yaml and kubectl apply -k
Tested on Rocky Linux 10.2 (Red Quartz) workstation
Package kubectl 1.36.3
Applies to Any host with kubectl configured; any Kubernetes cluster
Cert prep CKA · CKAD
Lab environment Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm
Privilege Normal user (no sudo required on the workstation)
Scope Base and overlay layout, kustomization.yaml, kubectl kustomize preview, namePrefix, namespace, labels, replicas, unified patches, images, configMapGenerator, secretGenerator, kubectl apply -k, kubectl diff -k, kubectl delete -k, comparison tables, and common path and patch mistakes. Does not cover Helm chart inflation, GitOps controllers, remote Git bases in depth, Kustomize plugins, custom transformers, live kubectl patch workflows, or CI/CD pipeline design.
Related guides Kubernetes ConfigMaps

Kustomize lets you keep one set of shared manifests and layer environment-specific changes on top. In this walkthrough you build a small nginx Deployment and Service as a base, customize development and production overlays, preview the rendered YAML, and apply each overlay with kubectl apply -k.


What Is Kustomize?

Kustomize customizes Kubernetes manifests without editing the original resource files in your base directory. Each directory that participates in a build contains a kustomization.yaml file listing resources, transformations, patches, and generators.

Kustomize ships inside kubectl, so you do not need a separate install for the commands in this article.

Bases vs overlays

Base Overlay
Contains reusable manifests Customizes a base
Shared across environments Environment-specific
Avoids duplicated YAML Adds patches and transformations
Can be rendered directly Usually represents a deployable environment

The usual layout splits concerns like this:

  • A base holds manifests shared by every environment.
  • An overlay references the base and adds environment-specific settings.
  • kubectl kustomize (or kustomize build) renders the final YAML before you apply it.

Build the Shared Base

Use a single application with a Deployment and a ClusterIP Service. Every overlay in this demo reuses the same base files.

Directory layout:

text
kustomize-demo/
├── base/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── kustomization.yaml
└── overlays/
    ├── development/
    │   ├── app.env
    │   ├── app-tier.txt
    │   └── kustomization.yaml
    └── production/
        ├── credentials.txt
        ├── patch-resources.yaml
        └── kustomization.yaml

Create the tree on your workstation:

bash
mkdir -p kustomize-demo/base kustomize-demo/overlays/development kustomize-demo/overlays/production
bash
cd kustomize-demo

All later kubectl kustomize and kubectl apply -k commands assume you are inside kustomize-demo.

Deployment and Service manifests

Save base/deployment.yaml:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.25.4
          ports:
            - containerPort: 80

Save base/service.yaml:

yaml
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

Keep version-specific image tags and replica counts out of the base when overlays will set them. Here the base uses nginx:1.25.4 and two replicas as sensible defaults that production will override.

Base kustomization

Save base/kustomization.yaml:

yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml
labels:
  - pairs:
      app: web
    includeSelectors: true
    includeTemplates: true

The base kustomization.yaml lists those files and applies the app: web label to selectors and Pod templates.

Preview the base

Rendering before apply shows exactly what Kustomize will send to the API server.

bash
kubectl kustomize base/

Sample output (trimmed):

output
apiVersion: v1
kind: Service
metadata:
  labels:
    app: web
  name: web
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: web
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web
  name: web
spec:
  replicas: 2

The output is plain Kubernetes YAML with no kustomization.yaml resource. Two documents appear because the base lists a Service and a Deployment.

If you install the standalone Kustomize binary, kustomize build base/ prints the same manifest set. This article uses kubectl kustomize as the primary command because it is already on the cluster admin workstation.


Create Development and Production Overlays

Create the target namespaces separately. namePrefix transforms the names of all rendered resources, so including a Namespace manifest inside a prefixed overlay would not leave the namespace named kustom-dev or kustom-prod.

bash
kubectl create namespace kustom-dev
bash
kubectl create namespace kustom-prod

Sample output:

output
namespace/kustom-dev created
namespace/kustom-prod created

Development overlay

Add environment inputs for configMapGenerator:

bash
printf 'APP_MODE=local\n' > overlays/development/app.env
bash
printf 'frontend' > overlays/development/app-tier.txt

Save overlays/development/kustomization.yaml:

yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: kustom-dev
namePrefix: dev-
labels:
  - pairs:
      environment: development
    includeSelectors: false
    includeTemplates: true
resources:
  - ../../base
replicas:
  - name: web
    count: 1
patches:
  - target:
      kind: Service
      name: web
    patch: |-
      apiVersion: v1
      kind: Service
      metadata:
        name: web
      spec:
        type: NodePort
  - target:
      kind: Deployment
      name: web
    patch: |-
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: web
      spec:
        template:
          spec:
            containers:
              - name: nginx
                envFrom:
                  - configMapRef:
                      name: app-config
configMapGenerator:
  - name: app-config
    literals:
      - APP_ENV=development
    envs:
      - app.env
    files:
      - APP_TIER=app-tier.txt

This overlay:

  • Sets namespace: kustom-dev on rendered resources
  • Adds the dev- prefix so dev objects do not collide with production names
  • Sets the environment: development label on templates
  • Scales the Deployment to one replica
  • Patches the Service to NodePort
  • Generates a ConfigMap and wires it into the Deployment through envFrom

Render the development overlay:

bash
kubectl kustomize overlays/development/

Sample output (trimmed):

output
apiVersion: v1
data:
  APP_ENV: development
  APP_MODE: local
  APP_TIER: frontend
kind: ConfigMap
metadata:
  labels:
    environment: development
  name: dev-app-config-d454g2957h
  namespace: kustom-dev
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: web
    environment: development
  name: dev-web
  namespace: kustom-dev
spec:
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web
    environment: development
  name: dev-web
  namespace: kustom-dev
spec:
  replicas: 1
  template:
    spec:
      containers:
      - envFrom:
        - configMapRef:
            name: dev-app-config-d454g2957h
        image: nginx:1.25.4
        name: nginx

The hash suffix on dev-app-config-d454g2957h comes from configMapGenerator. Kustomize rewrites configMapRef.name from the logical generator name app-config to the final prefixed, hashed name.

Production overlay

bash
printf 'prod-file-token' > overlays/production/credentials.txt

Save overlays/production/patch-resources.yaml:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  template:
    spec:
      containers:
        - name: nginx
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
          envFrom:
            - secretRef:
                name: app-secret

Save overlays/production/kustomization.yaml:

yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: kustom-prod
namePrefix: prod-
labels:
  - pairs:
      environment: production
    includeSelectors: false
    includeTemplates: true
resources:
  - ../../base
replicas:
  - name: web
    count: 4
images:
  - name: nginx
    newTag: "1.27.0"
patches:
  - path: patch-resources.yaml
secretGenerator:
  - name: app-secret
    literals:
      - API_KEY=prod-demo-key
    files:
      - FILE_TOKEN=credentials.txt

Production sets four replicas, bumps the nginx tag to 1.27.0, applies container resource requests, generates a Secret, and wires it into the Deployment through envFrom.

Render production output:

bash
kubectl kustomize overlays/production/ | grep -E 'replicas:|image:|cpu:|memory:|secretRef|envFrom'

Sample output:

output
replicas: 4
      - envFrom:
        - secretRef:
            name: prod-app-secret-m6kccbfbk7
        image: nginx:1.27.0
            cpu: 100m
            memory: 64Mi

The production overlay keeps the example small. Real teams often add more overlays per stage and wire image tags from CI build IDs rather than hand-editing kustomization.yaml for every release.


Customize Resources with Kustomize

Patches change selected fields on resources that Kustomize already loaded from the base or from resources. The unified patches field replaces older patchesStrategicMerge and patchesJson6902 entries.

Labels, namespaces, and name prefixes

  • namespace injects the target namespace into rendered resources
  • namePrefix and nameSuffix transform resource names after the base manifests are loaded
  • labels with includeTemplates: true add labels to Pod templates without changing selectors when includeSelectors is false

Create namespaces separately when you use namePrefix. Prefixing a Namespace manifest would change its name and break the namespace you intended to target.

Replica overrides

The replicas field is the simplest way to scale a Deployment by name:

yaml
replicas:
  - name: web
    count: 1

Kustomize matches the base Deployment name web before namePrefix transforms the live object name to dev-web or prod-web.

Inline and file-based patches

The development overlay sets the Service type inline:

yaml
patches:
  - target:
      kind: Service
      name: web
    patch: |-
      apiVersion: v1
      kind: Service
      metadata:
        name: web
      spec:
        type: NodePort

Inline patches work well for one or two field changes that you want visible beside other overlay settings.

Store larger or reusable patches in their own YAML files and list them under patches with path, as production does with patch-resources.yaml. Separate files are easier to review in pull requests and reuse across overlays.

Live edits with kubectl patch change objects already on the cluster. Kustomize patches change what gets rendered from Git. See kubectl command examples for imperative patch types when you need a one-off cluster edit without touching manifests.

Container image changes

The images field rewrites container image references without a patch on spec.template.spec.containers[].image.

Production overlay excerpt:

yaml
images:
  - name: nginx
    newTag: "1.27.0"

Kustomize replaces nginx:1.25.4 from the base with nginx:1.27.0 in the rendered Deployment. You can also set newName when the registry host or repository path changes.

That pattern is cleaner than maintaining a patch whose only job is to swap an image string. Build, tag, and push the image first, then point overlays at the tag your registry published. See build and push container images for the workstation side of that workflow.


Generate ConfigMaps and Secrets

Generator input types

configMapGenerator creates ConfigMap objects from literals, env files, or arbitrary file content at build time.

Development overlay generator:

yaml
configMapGenerator:
  - name: app-config
    literals:
      - APP_ENV=development
    envs:
      - app.env
    files:
      - APP_TIER=app-tier.txt
  • literals create individual keys
  • envs parse each KEY=VALUE line into a separate ConfigMap key
  • files with an explicit key such as APP_TIER=app-tier.txt store the file contents under that key

Using files: - settings.properties without a key remap stores the entire file under a key named settings.properties, not as separate parsed keys.

secretGenerator follows the same pattern for Secret data:

yaml
secretGenerator:
  - name: app-secret
    literals:
      - API_KEY=prod-demo-key
    files:
      - FILE_TOKEN=credentials.txt

For a Secret file to become a valid environment-variable key, map it explicitly with FILE_TOKEN=credentials.txt.

Reference generated objects

Reference the logical generator name in manifests. Kustomize automatically rewrites recognized references such as configMapRef.name and secretRef.name to the final prefixed, hashed name.

Development Deployment patch:

yaml
envFrom:
  - configMapRef:
      name: app-config

Production Deployment patch:

yaml
envFrom:
  - secretRef:
      name: app-secret

No separate “enable name reference updates” step is required.

Content hashes and old objects

Rendered ConfigMap and Secret names include a content hash by default (dev-app-config-d454g2957h and prod-app-secret-m6kccbfbk7 in this lab). When generator input changes, the suffix changes so a new object is created and recognized references update in the rendered Deployment.

Ordinary kubectl apply does not automatically prune the previous hashed object. Pruning or explicit deletion is a separate operation.

Treat kubectl kustomize output as sensitive when Secrets are rendered, even though values appear base64-encoded in YAML. See Kubernetes Secrets for mounting and access patterns.


Preview, Diff, Apply, and Verify

kubectl kustomize

kubectl kustomize prints the final multi-document YAML for a base or overlay directory. Use it whenever an overlay change does not look right on the cluster. The rendered document shows exact names after prefixes, namespace injection, and hash suffixes on generated objects.

kubectl apply -k

kubectl apply -k accepts a directory that contains kustomization.yaml, renders it with Kustomize, then applies the result. The -k option explicitly processes a kustomization directory and cannot be combined with -f.

Apply the development overlay:

bash
kubectl apply -k overlays/development/

Sample output:

output
configmap/dev-app-config-d454g2957h created
service/dev-web created
deployment.apps/dev-web created

Each line is a resource from the rendered set, not the kustomization.yaml file itself.

Wait until the Deployment becomes available before checking Pod status:

bash
kubectl rollout status deployment/dev-web -n kustom-dev --timeout=120s

Sample output:

output
deployment "dev-web" successfully rolled out

kubectl diff -k

kubectl diff -k compares the rendered overlay against objects already on the cluster.

bash
kubectl diff -k overlays/development/

Because the live resources match the rendered overlay, the command prints no diff and exits with status 0. Status 1 means differences were found, while a value greater than 1 indicates an error.

bash
kubectl get deploy,svc,pods -n kustom-dev

Sample output:

output
NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/dev-web   1/1     1            1           15s

NAME              TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
service/dev-web   NodePort   10.102.251.245   <none>        80:32618/TCP   15s

NAME                           READY   STATUS    RESTARTS   AGE
pod/dev-web-64b8d5fcf8-6jfnx   1/1     Running   0          14s

One replica, the dev- prefix, and NodePort match the development overlay settings.

Verify both environments

Apply the production overlay:

bash
kubectl apply -k overlays/production/

Sample output:

output
secret/prod-app-secret-m6kccbfbk7 created
service/prod-web created
deployment.apps/prod-web created

Wait for the production Deployment to finish rolling out:

bash
kubectl rollout status deployment/prod-web -n kustom-prod --timeout=120s

Sample output:

output
deployment "prod-web" successfully rolled out

Verify the core production customizations:

bash
kubectl get deployment prod-web -n kustom-prod -o jsonpath='replicas={.spec.replicas} image={.spec.template.spec.containers[0].image}{"\n"}'

Sample output:

output
replicas=4 image=nginx:1.27.0

Verify the generated Secret reference was rewritten to the hashed name:

bash
kubectl get deployment prod-web -n kustom-prod -o jsonpath='{.spec.template.spec.containers[0].envFrom[0].secretRef.name}{"\n"}'

Sample output:

output
prod-app-secret-m6kccbfbk7

The -k flag always points at a directory with kustomization.yaml, not at an individual manifest file.


Troubleshoot Common Kustomize Problems

Symptom Likely cause Fix
kustomization.yaml is not found -k points at a file or wrong directory Pass the directory that contains kustomization.yaml, for example kubectl apply -k overlays/development/
Resource path cannot be resolved Broken relative path in resources or patches Fix paths relative to the current kustomization.yaml (../../base from overlays/development/)
Patch does not match any resource Wrong kind, name, or namespace in target Match the base resource name before namePrefix / nameSuffix, and include namespace in target when needed
Generated name changes after editing input Content hash changed as designed Reference the logical generator name such as app-config; Kustomize rewrites recognized references. Disable the suffix only when a stable name is intentionally required
Overlay changes do not appear on the cluster Patch never matched, or wrong overlay applied Run kubectl kustomize on the overlay and inspect the rendered YAML before apply -k
kubectl apply -f ignores customizations -f bypasses overlay rendering, or -f on kustomization.yaml is invalid Use kubectl apply -k <directory> instead of applying individual files or kustomization.yaml with -f
Namespace not found on first apply namespace set but namespace object missing Create the namespace separately before apply when you do not include a Namespace manifest in the overlay

Disabling the hash suffix also removes the Pod-template name change that normally helps trigger a rollout. Generated ConfigMaps and Secrets receive the hash by default.


What's Next


References


Summary

You built a kustomize-demo tree with a shared nginx Deployment and Service base, then customized development and production overlays without copying YAML. Namespaces were created separately so namePrefix did not transform Namespace resources. Development added labels, a single replica, an inline Service patch, a generated ConfigMap wired through envFrom, and a hashed ConfigMap reference. Production raised replicas, swapped the nginx image tag, applied resource requests with a file patch, and generated a Secret referenced by the Deployment.

Preview with kubectl kustomize whenever an overlay change does not look right on the cluster. kubectl diff -k exits 0 when the rendered overlay matches the cluster. kubectl apply -k renders and applies the directory, while kubectl apply -f on ordinary files bypasses overlay transformations.

Kustomize fits teams that want plain Kubernetes manifests in Git without templating every field. For rollout mechanics after the image tag changes, continue with Deployments and rolling updates. For configuration and credentials consumed by Pods, see the ConfigMap and Secret guides linked above.


Frequently Asked Questions

1. What is the difference between a Kustomize base and an overlay?

A base holds shared manifests every environment reuses. An overlay references that base and adds environment-specific settings such as namespace, name prefix, replica count, patches, and generated ConfigMaps without editing the base files.

2. Should I use kubectl apply -f or kubectl apply -k for Kustomize?

Applying ordinary resource files with kubectl apply -f bypasses overlay transformations. Applying kustomization.yaml itself with -f normally fails because Kustomization is a local Kustomize configuration type, not an object served by the Kubernetes API. Use kubectl apply -k with the directory that contains kustomization.yaml.

3. Why does my generated ConfigMap name include a random suffix?

configMapGenerator and secretGenerator append a content hash by default so a changed value produces a new object name. Kustomize rewrites recognized references such as configMapRef.name from the logical generator name to the final hashed name.

4. Can I preview Kustomize output without applying it?

Yes. Run kubectl kustomize on the base or overlay directory to print the final multi-document YAML. The standalone kustomize build command does the same when the kustomize binary is installed separately.

5. What replaced patchesStrategicMerge in Kustomize?

Use the unified patches field in kustomization.yaml. You can supply a strategic merge patch inline or from a file, and set target kind and name when the patch must match one resource.

6. Does kubectl delete -k remove only the overlay changes?

kubectl delete -k deletes the resources in the overlay current rendered output. It deletes a Namespace only when a Namespace manifest is included in that output.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)