Kubernetes Configuration Management with Kustomize
Implements environment-specific Kubernetes configurations using Kustomize bases, overlays, strategic merge patches, and JSON patches. Manages deployment customization for dev, staging, and production without duplicating YAML manifests.
TL;DR Checklist
- Create a base
kustomization.yamlwithresources,commonLabels, andcommonAnnotations - Build at least one overlay directory (e.g.,
overlays/production/) with patches and env-specific values - Verify patches target the correct
kind,name, andpathfields - Test with
kustomize build <overlay-dir>before applying to any cluster - Use
patchesStrategicMergefor structural changes andpatchesJson6902for precise field edits - Store image tags and replica counts in overlay
kustomization.yamlusingimagesandreplicasdirectives - Keep the base cluster-neutral — no namespace, node selectors, or resource limits tied to an environment
When to Use
Use this skill when:
- Managing Kubernetes configurations across multiple environments (dev, staging, production) without YAML duplication
- Customizing a shared set of deployments, services, and configmaps per environment using overlays
- Replacing or supplementing Helm with a Git-native, Kubernetes-native config management approach
- Applying selective patches (replicas, resources, env vars, labels) to specific resources in an overlay
- Building infrastructure-as-code pipelines with CI/CD (e.g., Argo CD, Flux) that consume
kustomize buildoutput
When NOT to Use
Avoid this skill for:
- Package management with versioning and dependency resolution — use
helm chart developmentor ArgoCD Application CRDs instead - Highly templated configurations with complex conditional logic — consider Helm charts or KubeVault
- Small single-environment clusters where raw YAML manifests are simpler
- Manual per-file YAML editing — use
kustomize buildfor deterministic output
Core Workflow
Define the Base Layer — Create a base directory containing all shared Kubernetes manifests and a
kustomization.yamlthat declares them withresources:. AddcommonLabelsandcommonAnnotationsthat apply to every resource. Checkpoint: Runkustomize build ./base— all resources should render without errors and carry the common labels.Create Environment Overlay Directories — Under
overlays/<env>/(e.g.,overlays/production/), create akustomization.yamlthat references the base viabases:orresources:and declares overlay-specific customizations. Checkpoint: Each overlay must have anamespace:declaration and environment-specific image/replica overrides.Apply Patches — Use one of these patch strategies per overlay:
patchesStrategicMerge:— List patch files that fully or partially overwrite resourcespatches:(inline) — YAML inline patches in the overlay'skustomization.yamlpatchesJson6902:— JSON Patch (RFC 6902) targeting specific fields withtarget:selectors Checkpoint: Every patch must target a validkind: <resource-kind>andname: <resource-name>that exists in the base.
Customize Images, Replicas, and ConfigMaps — Declare image overrides with
images:(e.g.,name: myapp, newTag: v2.1.0), replica scaling withreplicas:, and ConfigMap key overrides withconfigMapGenerator:. Checkpoint: Runkustomize build ./overlays/productionand grep forreplicas: 3and the correct image tag to confirm overrides.Validate and Deploy — Run
kustomize build <overlay-dir> > /dev/nullto validate, then pipe output tokubectl applyor hand off to a GitOps controller. Checkpoint: Diff the rendered output against the previous deployment to confirm only intended fields changed.
Implementation Patterns / Reference Guide
Pattern 1: Base + Overlay Directory Structure
Standard Kustomize project layout separates shared resources from environment-specific customizations:
my-kustomize-project/
├── base/
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ ├── service.yaml
│ └── configmap.yaml
├── overlays/
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ └── replica-patch.yaml
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ └── replica-patch.yaml
│ └── production/
│ ├── kustomization.yaml
│ ├── resource-patch.yaml
│ ├── replica-patch.yaml
│ └── config-patch.yaml
Base kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- configmap.yaml
commonLabels:
app: my-service
managed-by: kustomize
commonAnnotations:
kustomize.tool.gitops.myorg.io/repo: my-kustomize-project
kustomize.tool.gitops.myorg.io/base: base
Production overlay kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: production
resources:
- ../../base
images:
- name: myregistry/my-service
newTag: "2.1.0"
newValue: "2.1.0"
replicas:
- name: my-service-deployment
count: 5
patches:
- path: replica-patch.yaml
- path: resource-patch.yaml
- path: config-patch.yaml
Pattern 2: Strategic Merge Patch vs JSON 6902 Patch
Use strategic merge patches for structural changes (adding new fields, updating nested objects) and JSON 6902 patches for precise field-level edits without overwriting the entire resource.
# ❌ BAD: Overwriting the entire deployment spec loses any fields
# not present in the patch file, making future base changes fragile
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-service-deployment
spec:
template:
spec:
containers:
- name: my-service
image: myregistry/my-service:2.1.0
ports:
- containerPort: 8080
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"
# ✅ GOOD: Strategic merge patch — only adds/overwrites the fields
# specified; the rest of the resource is inherited from base
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-service-deployment
spec:
template:
spec:
containers:
- name: my-service
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
env:
- name: LOG_LEVEL
value: "info"
- name: ENVIRONMENT
value: "production"
JSON 6902 Patch (targeted field edit):
# overlays/production/config-patch.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Patch
op: replace
path: /spec/template/spec/containers/0/env/2/value
value: "critical-path"
target:
kind: Deployment
name: my-service-deployment
apiVersion: apps/v1
namespace: production
Pattern 3: ConfigMap Generator in Overlay
Generate or override ConfigMap entries per environment without modifying the base manifest:
# overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: dev
resources:
- ../../base
configMapGenerator:
- name: my-service-config
literals:
- DATABASE_HOST=localhost
- DATABASE_PORT=5432
- CACHE_TTL=60
- FEATURE_FLAGS=debug-mode=true
options:
disableNameSuffixHash: "false"
configurations:
- kustomizeconfig.yaml
# overlays/dev/kustomizeconfig.yaml
# Field generators: replace values in existing ConfigMap keys
nameReference:
- kind: ConfigMap
fieldSpecs:
- kind: Deployment
path: spec/template/spec/volumes/0/configMap/name
name:
- kind: ConfigMap
path: spec/template/spec/volumes/0/configMap/name
Pattern 4: Variable Substitution with kustomize edit
Use kustomize edit for automated overlay customization from scripts or CI pipelines:
# Add an image override programmatically
kustomize edit set image myregistry/my-service:v3.0.0
# Add an env var to a ConfigMap generator
kustomize edit set configmap my-service-config --from-literal=FEATURE_FLAGS=dark-mode=true
# Set replicas
kustomize edit add patch --kind Deployment --name my-service-deployment \
--patch '[{"op":"replace","path":"/spec/replicas","value":10}]'
# Add a label to all resources
kustomize edit add label release-cycle=2026-w32
# Verify the result
kustomize build ./overlays/production
Pattern 5: Patch Order and Merging
Kustomize applies patches in declaration order. The last patch wins for conflicting fields. Understand this for debugging unexpected output:
Rendered base resource
↓
patchesStrategicMerge (applied in order listed)
↓
patches (inline, applied in order listed)
↓
patchesJson6902 (applied in order listed)
↓
final rendered output
# overlays/staging/kustomization.yaml
# Patches applied in this exact order:
patches:
# 1st: Set resource limits (strategic merge)
- path: limits-patch.yaml
# 2nd: Override specific env vars (JSON 6902)
- path: env-override.yaml
# 3rd: Add node affinity (strategic merge) — may conflict with limits-patch
- path: affinity-patch.yaml
# If limits-patch.yaml and affinity-patch.yaml both set spec.template.spec.containers,
# the affinity-patch wins because it appears last.
# Use `kustomize build ./overlays/staging` to inspect the merge result.
Constraints
MUST DO
- Always run
kustomize build <overlay-dir> > /dev/nullto validate rendering before applying to any cluster - Use
commonLabelsin the base for all resources that need consistent labeling (e.g.,app.kubernetes.io/managed-by: kustomize) - Declare overlays with explicit
namespace:— never rely on the default namespace in production overlays - Use
images:directive for image tag overrides instead of patching the container image field - Use
configMapGenerator:for environment-specific ConfigMap data rather than maintaining separate files - Place patches in the overlay directory, never in the base — bases must remain cluster- and environment-agnostic
- Use
patchesJson6902with explicittarget:selectors when you need field-level precision within a resource - Pin image names in
images:with the full registry path (e.g.,myregistry/my-service) to avoid ambiguous references - When using
replicas:, reference the deployment by the exactnamefrom the basekustomization.yaml
MUST NOT DO
- Never modify base manifests for environment-specific values — this defeats the overlay model entirely
- Never apply overlays with
namespace: defaultto production configurations - Never use
patchesStrategicMergeto overwrite an entire Deployment spec — use targeted strategic merge or JSON 6902 instead - Never rely on
nameSuffixHashfor production — usedisableNameSuffixHash: "true"when you need stable resource names - Never mix Helm values files with Kustomize patches in the same pipeline — pick one config management strategy
- Never use
kubectl apply -kwith user-editable overlay directories as CI input — always validate withkustomize buildfirst - Never skip the
patchesorder review — patch application order determines final rendered output and is a common source of bugs - Never hardcode cluster endpoints or credential references in base or overlay manifests — use external secret management
Troubleshooting Common Issues
| Symptom | Likely Cause | Fix |
|---|---|---|
build fails with no matches for kind Deployment |
Target selector in JSON 6902 patch references wrong kind |
Verify kind: Deployment and name: match a resource in the base |
| Patch has no visible effect | Patch declared after a later patch overwrites the same field | Reorder patches: so your patch applies last, or use JSON 6902 for precision |
image: override not applied |
Image name in images: doesn't match the container image: in base |
Use kustomize build and grep for image: to confirm the rendered value |
| ConfigMap name mismatch after generation | Default name suffix is appended (e.g., config-abc123) |
Set options.disableNameSuffixHash: "false" to retain the declared name |
| Replicas not changing | replicas: count name doesn't match deployment name in base |
Check kustomization.yaml resources: for the declared base name |
Related Skills
| Skill | Purpose |
|---|---|
cncf-kubernetes |
Core Kubernetes concepts, resources, and API patterns — prerequisite for understanding Kustomize targets |
cncf-helm |
Alternative configuration management with templating and package management — choose Kustomize or Helm, not both |
cncf-argo-cd |
GitOps controller that natively supports kustomize build as a source type for continuous deployment |
Live References
Authoritative documentation links for Kustomize configuration management.