Kustomize Overlays skill for Codex
One base, thin overlays, rendered diffs for every env. Codex organises Kubernetes YAML into a deployable base, thin dev, staging and production overlays, and opt-in components, choosing strategic merge or JSON 6902 patches by what is being changed. It renders every overlay before and after an edit and diffs them, so a change meant for staging cannot quietly reach production, and it migrates deprecated fields without touching immutable selectors.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- kustomize-overlays Installs to ~/.agents/skills/kustomize-overlays/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install kustomize-overlays The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project kustomize-overlays Installs to .agents/skills/kustomize-overlays/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/kustomize-overlays.zipunzip -o kustomize-overlays.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) kustomize-overlays PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $kustomize-overlays, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Structures Kubernetes configuration with Kustomize - a shared base, per-environment overlays, reusable components, strategic-merge and JSON 6902 patches, configMapGenerator and secretGenerator hash suffixes, image tag overrides, replacements and the helmCharts inflator - and diffs rendered output per environment. Use when the same manifests must differ across dev, staging and production, when overlays have drifted into copies, when migrating off deprecated fields like patchesStrategicMerge, commonLabels or vars, or when reviewing a kustomization.yaml.
Use it when
- The same app is deployed to several environments with small differences
- Overlays have grown into near-copies of the base
- kustomize build warns about deprecated fields
- You need a reviewable rendered diff per environment
Use something else when
- You are packaging an app for others to install with configurable values: use helm-charts
- The base manifests themselves need probes, resources or PDB design: use kubernetes-manifests
How to check the result
kustomize build succeeds for every overlay and the per-overlay diff against the pre-change render shows changes only in the environments you intended.
The mistake it prevents
Converting commonLabels to labels with includeSelectors flipped, which changes Deployment selectors and gets rejected by the API server on apply.
Done well, the base is a complete, deployable description of the app; each overlay holds
only what is genuinely different about its environment; and kustomize build for every
overlay is rendered and diffed in review, so nobody guesses what production receives.
Inspect first
- Read
AGENTS.mdand find everykustomization.yaml(rg --files -g 'kustomization.yaml'). - Learn who renders it:
kubectl apply -k, standalonekustomize build, Argo CD, or Flux.kubectlembeds an older Kustomize than the standalone binary; check both withkubectl version --clientandkustomize versionand write for the oldest one in use. - Render every overlay before editing and save the output as a baseline:
for o in overlays/*/; do kustomize build "$o" > "/tmp/before-$(basename "$o").yaml"; done
Layout
k8s/
├── base/
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ └── config.env
├── components/
│ ├── debug-sidecar/ # kind: Component, opt-in per overlay
│ └── pdb/
└── overlays/
├── dev/
│ └── kustomization.yaml
├── staging/
└── production/
├── kustomization.yaml
├── replicas-resources.yaml
└── config.env
Rules for the split:
| Put it in | When |
|---|---|
| base | True for every environment: containers, ports, probes, labels, default config |
| overlay | Differs per environment: replicas, resources, hostnames, image tag, namespace |
| component | An optional feature several overlays enable (PDB, HPA, sidecar, network policy) |
| separate base | The “variant” differs in more than half its fields; it is a different app |
Base
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
labels:
- pairs:
app.kubernetes.io/name: orders
includeSelectors: true # flipping this later changes immutable selectors
resources:
- deployment.yaml
- service.yaml
- ingress.yaml
configMapGenerator:
- name: orders-config
envs: [config.env]
Production overlay
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: shop-prod
resources:
- ../../base
components:
- ../../components/pdb
labels:
- pairs:
environment: production
includeSelectors: false # never add environment labels to selectors
images:
- name: registry.example.com/shop/orders
newTag: "1.14.2" # or digest: sha256:...
replicas:
- name: orders
count: 4
configMapGenerator:
- name: orders-config
behavior: merge
envs: [config.env]
patches:
- path: replicas-resources.yaml
- target:
kind: Ingress
name: orders
patch: |-
- op: replace
path: /spec/rules/0/host
value: orders.example.com
# overlays/production/replicas-resources.yaml (strategic merge patch)
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders
spec:
template:
spec:
containers:
- name: orders # merge key: must match the base container name
resources:
requests: { cpu: "1", memory: 1Gi }
limits: { memory: 1Gi }
Component:
# components/pdb/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
resources:
- pdb.yaml
Patch choice
| Change | Use |
|---|---|
| Add or change fields on a known object | Strategic merge patch file under patches: |
| Change one list item by index, remove a field, or patch CRDs without merge keys | JSON 6902 patch (op: replace/add/remove) with a target |
| Same patch across many objects | patches with target selecting by kind, labelSelector or name regex |
| Copy a value from one object into another (Service name into an env var) | replacements |
| Image tag per environment | images: (CI can run kustomize edit set image registry.example.com/shop/orders:1.14.3) |
Strategic merge on CRDs replaces whole lists because CRDs lack merge keys. Use JSON patches for CRD lists.
Deprecated fields to migrate
| Old | New |
|---|---|
patchesStrategicMerge, patchesJson6902 | patches (with path or patch, optional target) |
commonLabels | labels with explicit includeSelectors |
vars | replacements |
bases: | resources: |
kustomize edit fix rewrites many of these automatically; review the diff afterwards. A
commonLabels to labels move that changes includeSelectors alters Deployment
selectors, which the API server rejects for existing objects.
Generators
configMapGeneratorandsecretGeneratorappend a content hash to the name and rewrite references, so a config change rolls the Deployment. Keep that; do not setdisableNameSuffixHash: trueunless something outside Kustomize needs a fixed name.- Old hashed ConfigMaps accumulate in the cluster; Argo CD prunes them,
kubectl apply -kdoes not unless you use--prunewith a label selector. secretGeneratorwith literal values commits secrets to Git. Use it only with files that are gitignored or decrypted at build time (SOPS via KSOPS, or an external secrets operator). See$secrets-management.
Helm inside Kustomize
helmCharts:
- name: redis
repo: oci://registry-1.docker.io/bitnamicharts
version: 21.2.5
releaseName: cache
valuesFile: redis-values.yaml
Needs kustomize build --enable-helm (and network for the chart pull). Pin version.
Use this when you must patch something the chart’s values do not expose; otherwise
deploy the chart directly.
Hard rules
- Overlays never copy whole base files. If an overlay patch is larger than the base object, restructure.
- Relative paths only, and only inside the repo. The default load restrictor blocks files
outside the kustomization root; do not disable it with
--load-restrictor LoadRestrictionsNoneto work around a bad layout. - Names in patches must match the base name before
namePrefix/nameSuffixare applied. namespace:in an overlay overrides every namespaced object, including ones you meant to put elsewhere. Split those into their own kustomization.- Applying a production overlay is a deploy: show the rendered diff and get confirmation first.
Workflow
- [ ] 1. Render all overlays to baseline files
- [ ] 2. Make the change in the narrowest place (base, component, or one overlay)
- [ ] 3. Re-render every overlay and diff against its baseline
- [ ] 4. Validate rendered output with kubeconform
- [ ] 5. kubectl diff -k against the target cluster (needs access)
- [ ] 6. Apply only with user confirmation, or let GitOps sync it
Verification
for o in overlays/*/; do
n=$(basename "$o")
kustomize build "$o" > "/tmp/after-$n.yaml" || exit 1
diff -u "/tmp/before-$n.yaml" "/tmp/after-$n.yaml" > "/tmp/diff-$n.patch"; echo "$n: $(grep -c '^[+-][^+-]' /tmp/diff-$n.patch) changed lines"
kubeconform -strict -summary -ignore-missing-schemas "/tmp/after-$n.yaml"
done
kubectl diff -k overlays/staging
An unexpected change in an overlay you did not touch means the edit landed in the base or a shared component: confirm that was intended before continuing.
Output
**Change:** PDB moved from production overlay into components/pdb; enabled in staging and production
**Rendered diffs:** dev: none; staging: +PodDisruptionBudget orders; production: none (identical object)
**Migrations:** commonLabels -> labels (includeSelectors kept true, selectors unchanged)
**Checks:** kustomize build ok for 3 overlays, kubeconform clean, kubectl diff -k staging reviewed What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Kustomize Overlays"
short_description: "One base, thin overlays, rendered diffs for every env"
default_prompt: "$kustomize-overlays Split these Kubernetes manifests into a Kustomize base with dev and prod overlays" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.