Base and overlays
The base is ordinary Kubernetes YAML that you could kubectl apply on its own, plus a kustomization.yaml listing the files. Each overlay is another directory whose kustomization.yaml points at the base and says what differs.
app/
├── base/
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ └── service.yaml
└── overlays/
├── dev/
│ └── kustomization.yaml
└── prod/
├── kustomization.yaml
└── limits-patch.yamlresources:
- deployment.yaml
- service.yamlresources:
- ../../base # pull in the base
namespace: shop-prod # set on every namespaced resource
namePrefix: prod- # prod-web, prod-db, ... (references are rewritten too)
images:
- name: myapp # matches the image name in the base
newTag: "2.4.1"
replicas:
- name: web # the Deployment's name in the base
count: 5Render an overlay to see exactly what it produces, or apply it directly with -k:
kubectl kustomize overlays/prod # print the result, change nothing
kubectl apply -k overlays/prod # build and apply in one step
kubectl diff -k overlays/prod # what would change in the clusterLike docker compose config for Compose’s merged files, the render step is the habit worth building: read the output before applying it.
Patches
For changes the built-in fields don’t cover, add a patch. The default style is a strategic merge patch: a partial manifest naming the same kind and name, containing only the fields to change.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
template:
spec:
containers:
- name: web # lists of containers merge by name
resources:
limits:
memory: 512Mipatches:
- path: limits-patch.yamlThat’s the key difference from Compose’s file merging: Kubernetes lists such as containers are merged by their name key rather than appended, so the patch above changes the existing web container instead of adding a second one.
When you need an exact edit (remove an item, change one list index, patch many resources at once), use a JSON 6902 patch with a target:
patches:
- target:
kind: Deployment
name: web
patch: |-
- op: replace
path: /spec/template/spec/containers/0/imagePullPolicy
value: AlwaysGenerating ConfigMaps and Secrets
configMapGenerator builds a ConfigMap from literals or files and appends a content hash to its name. Change the contents and the name changes, so every Deployment that references it rolls automatically. See config & storage for why that matters. References to the generated name in the manifests are rewritten for you.
configMapGenerator:
- name: app-config
behavior: merge # extend the base's app-config instead of replacing it
literals:
- LOG_LEVEL=warnLabels, carefully
The older commonLabels field is deprecated in favor of labels. The reason is that commonLabels also rewrote selectors, and a Deployment’s spec.selector is immutable once created, so adding a label to an existing app could fail the apply. With labels you opt into selector changes explicitly:
labels:
- pairs:
env: prod
includeSelectors: false # label the objects, leave selectors aloneKustomize or Helm?
They solve different problems. Helm packages an app as a parameterized, versioned chart for other people to install. Kustomize adapts YAML you already own across your own environments, and the base stays valid on its own. Kustomize is usually the simpler fit for “the same app, three environments”; Helm fits distributing software or when you need logic and conditionals. The two also combine: you can post-process a Helm chart’s rendered output with a Kustomize overlay when you can’t change the chart itself.
Coming from Compose? The overlay idea is the Kubernetes counterpart of docker-compose.override.yml; see coming from Compose for the rest of the mapping, and lint the output with the Kubernetes linter.