Home DevOps Kustomize: Stop Copy-Pasting YAML — Declarative Config Management That Scales
Intermediate 4 min · July 18, 2026
Kustomize: Declarative Configuration Management

Kustomize: Stop Copy-Pasting YAML — Declarative Config Management That Scales

Kustomize declarative configuration management for Kubernetes: stop templating, start patching.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.

Follow
Production
production tested
July 18, 2026
last updated
2,466
articles · all by Naren
Before you start⏱ 30 min
  • Basic Kubernetes YAML syntax
  • Understanding of Deployments, Services, ConfigMaps
  • Familiarity with kubectl apply
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer

Kustomize lets you define a base Kubernetes config and then layer environment-specific patches without duplicating YAML. Run kubectl kustomize

to render the final config. It's built into kubectl since 1.14.

✦ Definition~90s read
What is Kustomize?

Kustomize is a Kubernetes-native configuration management tool that lets you declare environment-specific overlays on top of a base YAML configuration. No templates, no scripting — just pure YAML patching with kustomization.yaml files.

Think of Kustomize like a recipe card.
Plain-English First

Think of Kustomize like a recipe card. You write the base recipe (ingredients, cook time) once. Then for a dinner party you add a note: 'double the garlic, use red wine instead of white.' For a picnic you note: 'skip the sauce, pack cold.' You never rewrite the whole recipe. Kustomize does that for Kubernetes YAML — you keep one base, and overlays describe the differences.

You've been there: three environments — dev, staging, prod — each with its own copy of the same Deployment YAML. A replica count change means editing three files. A new label? Three files. Someone forgets to sync staging with prod, and now your canary is pointing at the wrong ConfigMap. This is the mess Kustomize was built to clean up. Not by introducing a templating language with loops and conditionals, but by letting you declare what changes per environment as pure YAML patches. No Helm charts, no Go templates, no custom scripts. Just kubectl kustomize . and you get exactly the config you need. After this article, you'll be able to structure a multi-environment Kubernetes repo using Kustomize overlays, apply patches without breaking a sweat, and know exactly when Kustomize is the right tool — and when it's not.

Why Kustomize Exists: The Pain of Copy-Paste YAML

Before Kustomize, teams managed environment-specific configs by duplicating YAML files. Dev, staging, prod — each had its own copy of the same Deployment, Service, ConfigMap. A simple change like adding a label meant editing N files. The result: config drift, human error, and late-night firefights. Helm tried to solve this with templates, but templates introduce complexity: conditionals, loops, and a steep learning curve. Kustomize takes a different approach. It says: keep one base config, and describe only the differences per environment. No templates, no scripting. Just YAML patching. This is declarative configuration management at its purest — you declare what you want, not how to get there.

base/kustomization.yamlDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# io.thecodeforge — DevOps tutorial

# Base kustomization.yaml — shared across all environments
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml
  - configmap.yaml

# Common labels for all resources
commonLabels:
  app.kubernetes.io/name: payment-service
  app.kubernetes.io/managed-by: kustomize

# Common annotations
commonAnnotations:
  version: "1.0.0"
  environment: base
🔥Senior Shortcut:
Use kustomize build . to see the fully rendered YAML. Pipe it to diff against the previous version in CI to catch silent patch failures.

Kustomize Structure: Base + Overlays

The core pattern is a directory tree with a base/ and overlays/ directory. The base contains the canonical YAML for your application — Deployment, Service, ConfigMap — plus a kustomization.yaml that lists them. Each overlay (dev, staging, prod) has its own kustomization.yaml that references the base and adds patches or customizations. This structure mirrors how you think about environments: dev is like prod but with fewer replicas, different resource limits, maybe a different ConfigMap. The overlay only describes the delta. This keeps your base clean and your environments consistent.

overlays/prod/kustomization.yamlDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# io.thecodeforge — DevOps tutorial

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

# Reference the base
resources:
  - ../../base

# Patch the Deployment to increase replicas and resource limits
patchesStrategicMerge:
  - deployment_patch.yaml

# Change the ConfigMap for prod
configMapGenerator:
  - name: app-config
    behavior: replace
    literals:
      - LOG_LEVEL=warn
      - DB_HOST=prod-db.internal

# Set namespace for all resources
namespace: prod

# Add a name prefix to avoid collisions
namePrefix: prod-
⚠ Production Trap:
If you use namePrefix or nameSuffix, remember that Services select Pods by label. The prefix changes the Service name but not the selector labels. Your Service might point to nothing. Always verify selectors after applying a prefix.

Patches: Strategic Merge vs. JSON Patch

Kustomize supports two patch types. patchesStrategicMerge is the most common: you write a partial YAML that gets merged into the target resource. It works like a three-way merge — you only specify the fields you want to change. patchesJson6902 uses JSON Patch (RFC 6902) operations: op: replace, add, remove. Use strategic merge for simple field overrides (replicas, image tag). Use JSON patch when you need to modify lists (like container args) or remove fields. Strategic merge can't delete a field; JSON patch can. Both are declared in kustomization.yaml.

overlays/prod/deployment_patch.yamlDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# io.thecodeforge — DevOps tutorial

# Strategic merge patch: only specify fields to override
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
spec:
  replicas: 5
  template:
    spec:
      containers:
        - name: app
          resources:
            requests:
              memory: "1Gi"
              cpu: "500m"
            limits:
              memory: "4Gi"
              cpu: "2"
💡Interview Gold:
Strategic merge patches work by matching on apiVersion, kind, metadata.name. If the name doesn't match, the patch is silently ignored. Always check that your patch's metadata.name matches the base resource name.

ConfigMap and Secret Generation

One of Kustomize's killer features is generating ConfigMaps and Secrets from literals or files. Instead of writing a ConfigMap YAML by hand, you declare configMapGenerator with key-value pairs or file references. Kustomize creates the ConfigMap with a content-hash suffix, so any change triggers a rolling update of your Deployments. This solves the classic problem: you update a ConfigMap but Pods don't pick it up because the name hasn't changed. With Kustomize, the name changes automatically when content changes. Same for Secrets. Use behavior: merge to add keys from an overlay, or behavior: replace to completely replace the base ConfigMap.

overlays/dev/kustomization.yamlDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# io.thecodeforge — DevOps tutorial

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

resources:
  - ../../base

configMapGenerator:
  - name: app-config
    behavior: merge
    literals:
      - LOG_LEVEL=debug
      - DB_HOST=dev-db.internal

secretGenerator:
  - name: db-credentials
    behavior: replace
    literals:
      - username=dev_user
      - password=dev_pass

# Disable name suffix for dev to keep names short
nameSuffix: ""
⚠ Never Do This:
Don't put secrets in literals in your repo. Use files or envs pointing to encrypted files (e.g., sops-encrypted). Or use an external secrets operator. secretGenerator with literals in plaintext is a security incident waiting to happen.

Kustomize Components: Reusable Patches

Components are a newer feature (stable in Kustomize v4) that let you define reusable patches that can be included by multiple overlays. Think of them as mixins. For example, a component that adds a sidecar container for logging, or a component that attaches a persistent volume. Components are declared in their own directory with a kustomization.yaml using kind: Component. Overlays include them via components: field. This is great for enforcing cross-cutting concerns like monitoring, logging, or security sidecars across all environments.

components/logging-sidecar/kustomization.yamlDEVOPS
1
2
3
4
5
6
7
# io.thecodeforge — DevOps tutorial

apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component

patchesStrategicMerge:
  - sidecar_patch.yaml
🔥Senior Shortcut:
Use components for mandatory additions (like a logging sidecar) that every environment must have. For optional features, use overlays. Components cannot be toggled off once included — they're always applied.

Kustomize vs. Helm: When to Use Which

This is the debate that won't die. Here's the truth: Kustomize is better when your configs are mostly static with small per-environment variations. It's pure YAML — no learning curve beyond Kubernetes itself. Helm is better when you need to package and distribute an application with configurable parameters, especially if you're publishing a chart for others to use. Helm's templating allows complex logic (loops, conditionals) that Kustomize can't do. But that complexity is also its weakness: Helm charts are harder to debug, and template errors can produce invalid YAML. My rule: if you control the deployment and just need env-specific tweaks, use Kustomize. If you're building a reusable package for a community, use Helm. Or use both — Helm to package, Kustomize to customize per environment.

comparison_table.devopsDEVOPS
1
2
3
# io.thecodeforge — DevOps tutorial

# Comparison table is in the JSON structure below this code block.
💡Production Trap:
Don't use Helm's --set to override values in production. It's not auditable. Instead, commit a values.yaml per environment and use Kustomize to apply environment-specific patches on top of the Helm chart output.

Production Patterns: Multi-Tenant and Multi-Cluster

In production, you often need to manage configs for multiple tenants or clusters. Kustomize scales well here. For multi-tenant, create an overlay per tenant with its own namespace, name prefix, and ConfigMap. For multi-cluster, you can have a top-level directory per cluster (us-east, eu-west) with overlays per environment inside. Use kustomize build with --load_restrictor none if you need to reference resources outside the kustomization root (but be careful — this can break reproducibility). Another pattern: use kustomize edit in CI to programmatically modify the kustomization.yaml, then build and apply. This is how you automate canary deployments or feature flags.

overlays/tenant-alpha/kustomization.yamlDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# io.thecodeforge — DevOps tutorial

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

resources:
  - ../../base

namespace: tenant-alpha
namePrefix: alpha-

patchesStrategicMerge:
  - deployment_patch.yaml

configMapGenerator:
  - name: app-config
    behavior: merge
    literals:
      - TENANT_ID=alpha
      - FEATURE_NEW_PAYMENT=true
⚠ The Classic Bug:
When using namePrefix, remember that metadata.name of all resources gets prefixed, but spec.selector.matchLabels in Deployments and Services do NOT get prefixed. If two overlays share the same base, they might have conflicting selectors. Always set commonLabels in each overlay to include a unique label like tenant: alpha.

Kustomize in CI/CD: GitOps and ArgoCD

Kustomize is a natural fit for GitOps workflows. Tools like ArgoCD and Flux natively support Kustomize. You point ArgoCD at a Git repo with a kustomization.yaml, and it automatically syncs the rendered config. This means your Git repo is the single source of truth — no manual kubectl apply. In CI, you can run kustomize build to validate the config and even diff it against the live cluster. Use kustomize build . | kubectl diff -f - to see what would change. This catches errors before they hit production. For multi-environment pipelines, use separate branches or directories. I prefer directories: clusters/prod/, clusters/staging/ — each with its own kustomization.yaml that references a shared base.

ci-pipeline.shDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# io.thecodeforge — DevOps tutorial

#!/bin/bash
set -euo pipefail

# Validate Kustomize config for each environment
for env in dev staging prod; do
  echo "Validating $env..."
  kustomize build overlays/$env > /dev/null
  echo "$env config is valid"
done

# Diff against live cluster (requires kubectl context)
kustomize build overlays/prod | kubectl diff -f - || true

# Apply (in production, use a GitOps tool instead)
# kustomize build overlays/prod | kubectl apply -f -
💡Interview Gold:
ArgoCD supports Kustomize natively. Set path: overlays/prod in your Application spec. ArgoCD will run kustomize build and sync the result. No need to commit the rendered YAML.

When Kustomize Breaks: Limitations and Workarounds

Kustomize is not a silver bullet. It struggles with complex transformations that require conditionals or loops. For example, you can't say 'if environment is prod, add this sidecar, else skip it' — you'd need two overlays. Also, Kustomize's patch matching is fragile: it matches on apiVersion, kind, and name. If you rename a resource in the base, all patches break silently. Another pain point: patchesStrategicMerge can't delete a field; you need JSON patch for that. And JSON patch with array indices is brittle. For very complex configs, consider using kustomize with helm via helm template piped into kustomize. Or use a tool like jsonnet or yq for heavy lifting. Know when to walk away.

workaround-json-patch.yamlDEVOPS
1
2
3
4
5
6
7
8
9
10
11
# io.thecodeforge — DevOps tutorial

# JSON patch to remove a container from a Deployment
patchesJson6902:
  - target:
      version: v1
      kind: Deployment
      name: payment-service
    patch: |-
      - op: remove
        path: /spec/template/spec/containers/1
⚠ Never Do This:
Don't rely on array indices in JSON patches. If the base changes (e.g., a container is added), the index shifts and your patch breaks. Use strategic merge with a unique container name instead, or use a label-based selector.

Debugging Kustomize: Common Errors and Fixes

The most common error: error: json: unknown field "patchesStrategicMerge" — you're using an old Kustomize version. Upgrade to v4+. Another: error: no matches for Id — your patch target doesn't match any resource. Check apiVersion, kind, and name. Use kustomize build to see the rendered resources and their names. If you see error: accumulating resources: accumulation err='...', it's usually a YAML syntax error in one of your files. Run yamllint on all YAML files. For namePrefix issues, use kustomize build and grep for the prefix to verify it's applied. And always, always run kustomize build before applying — never apply a kustomization without reviewing the output.

debug-commands.shDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
# io.thecodeforge — DevOps tutorial

# Render and inspect
kustomize build overlays/prod | less

# Check for specific resource
kustomize build overlays/prod | grep -A 20 "kind: Deployment"

# Validate YAML syntax
find overlays -name "*.yaml" -exec yamllint {} +

# Diff against live cluster
kustomize build overlays/prod | kubectl diff -f -
🔥Senior Shortcut:
Add a Makefile target: validate: kustomize build overlays/prod > /dev/null && yamllint overlays/*/.yaml. Run it in CI before every deploy.
● Production incidentPOST-MORTEMseverity: high

The 4GB Container That Kept Dying

Symptom
A payment processing service kept OOM-killing every 20 minutes in production. Dev and staging ran fine.
Assumption
The team assumed a memory leak in the application code.
Root cause
The base Deployment had resources.requests.memory: 512Mi and limits.memory: 1Gi. The prod overlay was supposed to bump limits to 4Gi but the patch used patchesStrategicMerge with an incorrect indentation — it never applied. The pod ran with 1Gi limit and OOM'd under load.
Fix
Fixed the patch YAML indentation. Added a kustomize build step in CI that diffs the rendered output against expected resource limits. Now every PR shows the final memory limit in the diff.
Key lesson
  • Always render and review the final Kustomize output before applying.
  • A mis-indented patch silently fails.
Production debug guideSystematic recovery paths for the failure modes engineers actually hit.3 entries
Symptom · 01
Pod OOM kills despite setting resource limits in overlay
Fix
1. Run kustomize build overlays/prod | grep -A 10 'resources:' to verify limits are applied. 2. Check for indentation errors in patch YAML. 3. Ensure patch metadata.name matches base resource name.
Symptom · 02
Service not routing traffic after deploying with namePrefix
Fix
1. Run kubectl get svc -n <namespace> and note the Service name. 2. Run kubectl get pods -n <namespace> --show-labels and check if Pod labels match Service selector. 3. Add commonLabels to overlay to ensure label consistency.
Symptom · 03
ConfigMap update not triggering Pod restart
Fix
1. Check if ConfigMap is generated via configMapGenerator (has hash suffix). 2. Verify Deployment references the ConfigMap by name (not hardcoded). 3. Run kustomize build and confirm the ConfigMap name changed.
★ Kustomize Triage Cheat SheetFirst-response commands for when things go wrong — copy-paste ready.
Patch not applied — `kustomize build` shows no changes
Immediate action
Check patch target name and apiVersion
Commands
kustomize build overlays/prod | grep -A 5 'kind: Deployment'
cat overlays/prod/deployment_patch.yaml
Fix now
Ensure metadata.name in patch matches base resource name (including any namePrefix).
YAML validation error — `error: json: unknown field`+
Immediate action
Check Kustomize version
Commands
kustomize version
yamllint overlays/**/*.yaml
Fix now
Upgrade Kustomize to v4+ or fix YAML syntax.
Resource not found after apply — `Error from server: NotFound`+
Immediate action
Check namespace and namePrefix
Commands
kustomize build overlays/prod | grep -E '^(apiVersion|kind|metadata| name:| namespace:)'
kubectl get all -n <namespace>
Fix now
Ensure overlay sets correct namespace and namePrefix.
ConfigMap not updating Pods — old config still used+
Immediate action
Check if ConfigMap has hash suffix
Commands
kustomize build overlays/prod | grep -A 5 'kind: ConfigMap'
kubectl get configmap -n <namespace>
Fix now
Use configMapGenerator with behavior: merge or replace to get automatic hash suffix.
Feature / AspectKustomizeHelm
Configuration approachDeclarative YAML patchingGo templates with values
Learning curveLow (pure YAML)Medium (templating language)
ReusabilityComponents, overlaysCharts, dependencies
Complex logicNone (no conditionals/loops)Full templating (if, range, etc.)
DebuggingEasy (rendered YAML is clean)Hard (template errors, whitespace issues)
Package distributionNot designed for itBuilt for it (chart repos)
GitOps supportNative (ArgoCD, Flux)Native (with Helm operator)
Secret managementBasic (generators)Advanced (plugins, sops)
CommunityGrowing, Kubernetes SIGLarge, CNCF graduated
⚙ Quick Reference
9 commands from this guide
FileCommand / CodePurpose
basekustomization.yamlapiVersion: kustomize.config.k8s.io/v1beta1Why Kustomize Exists
overlaysprodkustomization.yamlapiVersion: kustomize.config.k8s.io/v1beta1Kustomize Structure
overlaysproddeployment_patch.yamlapiVersion: apps/v1Patches
overlaysdevkustomization.yamlapiVersion: kustomize.config.k8s.io/v1beta1ConfigMap and Secret Generation
componentslogging-sidecarkustomization.yamlapiVersion: kustomize.config.k8s.io/v1alpha1Kustomize Components
overlaystenant-alphakustomization.yamlapiVersion: kustomize.config.k8s.io/v1beta1Production Patterns
ci-pipeline.shset -euo pipefailKustomize in CI/CD
workaround-json-patch.yamlpatchesJson6902:When Kustomize Breaks
debug-commands.shkustomize build overlays/prod | lessDebugging Kustomize

Key takeaways

1
Kustomize is pure YAML patching
no templates, no scripting. The rendered output is clean Kubernetes YAML you can review.
2
Always run kustomize build and review the output before applying. A mis-indented patch silently fails.
3
Use configMapGenerator and secretGenerator to get automatic rolling updates when config changes.
4
Kustomize is great for environment-specific overrides but breaks down with complex logic. Know when to use Helm or other tools.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does Kustomize handle resource ordering when applying multiple resou...
Q02SENIOR
When would you choose Kustomize over Helm in a production system?
Q03SENIOR
What happens when a strategic merge patch has a field that doesn't exist...
Q04JUNIOR
What is the difference between `patchesStrategicMerge` and `patchesJson6...
Q05SENIOR
You have a base Deployment with two containers. You want to remove one c...
Q06SENIOR
How would you design a Kustomize structure for a multi-cluster, multi-en...
Q01 of 06SENIOR

How does Kustomize handle resource ordering when applying multiple resources?

ANSWER
Kustomize does not guarantee ordering. It sorts resources by kind (e.g., Namespaces first, then Deployments, etc.) but within the same kind, order is alphabetical by name. For strict ordering, use kubectl apply with multiple files or a tool like kapp.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
What is Kustomize and how does it work?
02
What's the difference between Kustomize and Helm?
03
How do I use Kustomize to manage multiple environments?
04
How do I debug a Kustomize patch that isn't applying?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.

Follow
Verified
production tested
July 18, 2026
last updated
2,466
articles · all by Naren
🔥

That's Kubernetes. Mark it forged?

4 min read · try the examples if you haven't

Previous
Kubernetes Pod Disruption Budgets: High Availability
36 / 43 · Kubernetes
Next
Kubernetes LimitRanges and ResourceQuotas: Resource Governance