Tools · Kustomize

Kustomize

Kustomize is template-free configuration customisation for Kubernetes, built straight into kubectl: you keep one set of ordinary, valid YAML manifests as a base, then declare small overlays that patch it per environment — different replica counts for dev, a different image tag for prod, an extra label everywhere. It solves the platform problem of “we now have the same app copy-pasted into four folders and they have quietly diverged,” without asking you to learn a templating language or ship a package.

☺ Explain it like I’m 10

Imagine you draw one really good picture of a house. Now your teacher wants three versions: one with a red door, one with two chimneys, one with the name “PROD” on the mailbox. You could redraw the whole house three times and hope they stay the same — or you could keep the one good drawing and put three sheets of tracing paper over it, each with just the little change on it. Kustomize is the tracing paper. The original drawing never changes, each sheet is tiny and easy to read, and when you hold one up to the light you get the finished picture.

🦫Your host for this topic: Benny the Beaver — Benny builds with layers. He lays one solid base of logs, then adds exactly the sticks each pond needs, and never rebuilds the whole dam just to change the water level.

What Kustomize is and the problem it solves

☺ Like you’re 10: It keeps one copy of your YAML and lets you stick tiny “change just this bit” notes on top for each environment.

Kustomize is a Kubernetes-native configuration tool, now maintained under kubernetes-sigs/kustomize and embedded in kubectl since v1.14. Its defining property is stated in its own tagline: it is template-free. There is no {{ .Values.something }}, no Go templating, no rendering step that turns not-quite-YAML into YAML. Everything you write is real, valid, schema-checkable Kubernetes YAML — plus one small extra file, kustomization.yaml, that says which files to include and how to transform them.

The problem before Kustomize

Every team that ships to more than one environment hits the same wall, described in detail in Configuration Management: dev, staging and prod need the same Deployment with three small differences. The naive answer is three folders of copy-pasted YAML. Within a month, prod has a security context that dev doesn’t, staging has a stale liveness probe, and the environments only resemble each other by coincidence. Whatever fixes this must let the shared 95% live in exactly one place.

Patches, not parameters

Kustomize’s answer is different from Helm’s. Helm asks the author to predict every knob a consumer might want and expose it as a parameter in values.yaml; anything the author didn’t parameterise is out of reach. Kustomize asks nothing of the author: because the base is plain YAML, the overlay can patch any field of any object, whether or not anyone anticipated it. You trade discoverability (there is no single list of “supported settings”) for reach (nothing is off limits).

◆ Key idea

Helm parameterises before the YAML exists — placeholders filled in at render time. Kustomize patches after the YAML exists — real objects merged with real objects. That single difference explains almost every other contrast between them: why Kustomize has no release history, why it can customise a chart it doesn’t own, and why its failure messages are about merges rather than about templates.

Where it fits in a platform

☺ Like you’re 10: Kustomize sits in the drawer where you keep “what should be running,” right next to the robot that reads that drawer.

Kustomize lives in the delivery plane of a platform architecture — specifically in the authoring layer, between “a developer wants a change” and “a reconciler applies it.” It produces manifests; it does not talk to a cluster, hold state, run controllers, or know what a release is. That narrowness is a feature: it composes with everything downstream.

Neighbours: Helm, Argo CD and Flux

Its closest neighbour is Helm — the alternative and, often, the partner. Downstream, both Argo CD and Flux render Kustomize for you: point an Argo CD Application at a path containing a kustomization.yaml and its repo-server runs the build automatically; Flux has an entire controller named for it (kustomize-controller, reconciling a Kustomization custom resource — note that this CR is not the same thing as the kustomization.yaml file, a naming collision that trips up nearly everyone). Upstream, the base you write is the artefact your CI pipeline mutates when it bumps an image tag.

CNPE domain relevance

Kustomize is not on the official CNPE tool list — but it is the default rendering mechanism inside two tools that are (Argo CD and Flux), and it is part of kubectl itself, which you will use throughout the performance-based tasks. It supports Domain 2 — GitOps & Continuous Delivery (declarative, versioned desired state; per-environment promotion) and touches Domain 1 wherever the reference architecture needs one manifest set fanned out across clusters. Practise it in the GitOps practice tasks.

The base and overlays pattern

The pattern that carries a whole platform is deliberately boring. One base/ directory holds the canonical manifests. One overlays/<env>/ directory per environment holds a kustomization.yaml that references the base and applies a handful of differences. Promotion from staging to prod becomes a diff of a few lines in one small file — reviewable by a human in thirty seconds, which is exactly the property GitOps depends on.

base/ deployment · service kustomization.yaml overlays/dev replicas: 1 overlays/staging overlays/prod replicas: 6 · image tag 🦫 kustomize build generate → transform rendered YAML plain manifests no placeholders left resources patches emit 🤖 kubectl apply -k · Argo CD · Flux the cluster gets the finished YAML every input is valid YAML — the build is a merge, not a render

How it works — the kustomization file and the build

☺ Like you’re 10: One little file lists the YAML to pick up and the changes to make. Running “build” does the picking up and the changing, in a fixed order.

There are no controllers, no CRDs installed into your cluster and no server-side component — a point worth stating clearly, because people meeting it after Argo CD expect one. Kustomize is a pure function: directory in, YAML out. The only API object it defines is the Kustomization file kind (apiVersion: kustomize.config.k8s.io/v1beta1), which never gets applied to a cluster, and the Component kind (v1alpha1), which is a reusable fragment of one.

Generators, then transformers

A build runs in two phases, and knowing the order explains most surprises. First, generators run: configMapGenerator and secretGenerator manufacture new objects from literals and files. Then transformers run over everything — the generated objects and the ones loaded from resources alike — in a defined sequence: the patches first, then the metadata transformers (namespace, namePrefix/nameSuffix, labels, commonAnnotations), then replicas and images, and finally the name-reference transformer, which walks the graph and fixes up every place one object refers to another by name.

That last transformer is the quiet hero. When namePrefix: prod- renames a ConfigMap to prod-app-config, the name-reference transformer also rewrites the configMapKeyRef inside the Deployment that consumes it. Kustomize understands the Kubernetes object graph, which is precisely what a text-substitution templating engine cannot do.

The fields you will actually use

FieldWhat it doesNote
resourcesFiles, directories or remote URLs to load — including other kustomization directories (a base)Replaced the old bases field
namespaceSets metadata.namespace on every namespaced objectAlso fixes ServiceAccount references in RoleBindings
namePrefix / nameSuffixPrepends/appends to every resource nameReferences are fixed up automatically
labelsAdds labels; includeSelectors and includeTemplates control how far they reachPrefer this over commonLabels
commonLabelsAdds labels and injects them into selectorsLegacy — see the gotcha below
commonAnnotationsAdds annotations to every objectNever touches selectors
imagesRewrites container images by namenewName / newTag / digestThe field CI usually bumps
replicasOverrides replica counts by workload nameShorter than a patch for the common case
patchesStrategic-merge or JSON 6902 patches with an optional target selectorThe modern, unified field
patchesStrategicMerge / patchesJson6902The older split fieldsDeprecated — migrate to patches
configMapGenerator / secretGeneratorBuilds ConfigMaps/Secrets from literals, files or env filesAppends a content hash to the name
generatorOptionsdisableNameSuffixHash, plus labels/annotations/immutable for generated objectsApplies to all generators in the file
replacementsCopies a value from one object’s field into fields of other objectsThe supported successor to vars
componentsPulls in reusable, optional slices of configurationkind: Component, v1alpha1
helmChartsInflates a Helm chart, then transforms the outputNeeds --enable-helm

Two patch dialects

A strategic merge patch is a partial Kubernetes object: you write the same YAML shape as the target, filling in only the fields you want changed, and Kubernetes’ merge-key rules combine them (containers merge by name, ports by containerPort). It reads beautifully and is the right default. A JSON 6902 patch is a list of surgical operations — add, replace, remove, copy, move, test — each addressed by a JSON Pointer path such as /spec/template/spec/containers/0. Reach for 6902 when you need to delete an element or write to a precise list index, which strategic merge handles awkwardly.

The resources you will actually write

☺ Like you’re 10: Here is a real base, a real prod overlay, and the little patch files they point at.

The base

Start with the base. Note that base/deployment.yaml is a completely ordinary manifest — you could kubectl apply -f it on its own and it would work. That is the whole promise of template-free configuration.

# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:                        # ordinary manifests, valid on their own
  - deployment.yaml
  - service.yaml
  - serviceaccount.yaml

labels:                           # modern replacement for commonLabels
  - pairs:
      app.kubernetes.io/name: checkout
      app.kubernetes.io/part-of: storefront
    includeSelectors: false       # do NOT write these into Deployment selectors

images:
  - name: checkout                # matches spec.containers[].image "checkout:..."
    newName: ghcr.io/acme/checkout
    newTag: 1.4.2                 # CI bumps this line, or uses `kustomize edit`

configMapGenerator:
  - name: checkout-config
    literals:
      - LOG_LEVEL=info
      - FEATURE_GIFTWRAP=false
    # → emits ConfigMap "checkout-config-9fh72tk4bd" (content hash suffix)

The production overlay

Now the production overlay. It references the base by relative path, lands everything in one namespace, prefixes names so prod objects are unmistakable, and applies two patches — one strategic-merge from a file, one JSON 6902 written inline against a target selector.

# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: storefront-prod
namePrefix: prod-

resources:
  - ../../base                    # the base — a relative path, or a pinned Git URL

components:
  - ../../components/pdb          # optional, reusable slice (see below)

commonAnnotations:
  platform.acme.io/owner: team-checkout

replicas:
  - name: checkout                # the base name, before namePrefix is applied
    count: 6

images:
  - name: ghcr.io/acme/checkout
    newTag: 1.4.2

patches:
  - path: resources-patch.yaml    # strategic merge; target inferred from the file

  - target:                       # JSON 6902, inline, with an explicit selector
      kind: Deployment
      labelSelector: app.kubernetes.io/part-of=storefront
    patch: |-
      - op: replace
        path: /spec/template/spec/containers/0/imagePullPolicy
        value: IfNotPresent
      - op: remove
        path: /spec/template/spec/containers/0/env/0

configMapGenerator:
  - name: checkout-config
    behavior: merge               # merge into the base's generated ConfigMap
    literals:
      - LOG_LEVEL=warn
      - FEATURE_GIFTWRAP=true

A patch file and a reusable component

The strategic-merge patch file is the part people find most pleasant: it looks exactly like the object it is changing, minus everything you don’t care about. Alongside it, a Component — a fragment that overlays can opt into, useful for cross-cutting concerns like “add a PodDisruptionBudget and a topology spread constraint,” which you want on prod and staging but not on ephemeral preview environments.

# overlays/prod/resources-patch.yaml  — a strategic merge patch
apiVersion: apps/v1
kind: Deployment
metadata:
  name: checkout                  # identifies the target; pre-prefix name
spec:
  template:
    spec:
      containers:
        - name: checkout          # merge key for the containers list
          resources:
            requests: { cpu: 500m, memory: 512Mi }
            limits:   { memory: 512Mi }
---
# components/pdb/kustomization.yaml — reusable, opt-in slice
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component                   # NOTE: Component, not Kustomization

resources:
  - pdb.yaml

patches:
  - target: { kind: Deployment }
    patch: |-
      - op: add
        path: /spec/template/spec/topologySpreadConstraints
        value:
          - maxSkew: 1
            topologyKey: topology.kubernetes.io/zone
            whenUnsatisfiable: ScheduleAnyway
            labelSelector:
              matchLabels: { app.kubernetes.io/name: checkout }
⚠ secretGenerator is not encryption

secretGenerator base64-encodes your literals into a Secret — and base64 is encoding, not encryption. Anything you put in literals: or a referenced .env file lands in Git in trivially reversible form. Generate the shape of the Secret with Kustomize if you like, but source the values from External Secrets, Sealed Secrets or SOPS — the full menu is on Secrets Management.

Day-to-day commands

☺ Like you’re 10: Three commands do almost everything: show me the result, show me what would change, and do it.

Render, diff, apply

The single most important habit is to render before you apply. kubectl kustomize prints the finished YAML to stdout without touching the cluster, which turns “I hope my patch worked” into “I can see that my patch worked.”

# --- render, inspect, diff (safe: nothing is applied) ---
kubectl kustomize overlays/prod              # print the built manifests
kustomize build overlays/prod                # same, with the standalone binary
kustomize build overlays/prod --enable-helm  # also inflate helmCharts entries

kubectl kustomize overlays/prod | kubectl apply --dry-run=server -f -
kubectl diff -k overlays/prod                # what WOULD change in the cluster

# --- apply and remove ---
kubectl apply -k overlays/prod               # build, then apply
kubectl delete -k overlays/prod              # build, then delete those objects

# --- edit the kustomization file from CI, no YAML surgery ---
cd overlays/prod
kustomize edit set image ghcr.io/acme/checkout=ghcr.io/acme/checkout:1.4.3
kustomize edit set replicas checkout=8
kustomize edit set namespace storefront-prod
kustomize edit add resource networkpolicy.yaml
kustomize edit fix                           # migrate deprecated fields in place

# --- sanity checks worth wiring into CI ---
kustomize build overlays/prod | kubeconform -strict -            # schema-validate
diff <(kustomize build overlays/staging) <(kustomize build overlays/prod)
kubectl version --client                     # note the EMBEDDED kustomize version

How Argo CD and Flux invoke it

You will rarely run kustomize build in production yourself. An Argo CD Application whose source.path contains a kustomization.yaml is detected automatically and rendered by the repo-server; Argo CD also exposes spec.source.kustomize for overriding images, name prefixes and replicas from the Application itself. In Flux, a Kustomization custom resource with spec.path pointed at your overlay does the same via kustomize-controller. Full command lists live in the command reference.

Gotchas and failure modes

☺ Like you’re 10: Four things bite everyone: the changing ConfigMap name, labels that break Deployments, patches that quietly do nothing, and version differences.

The ConfigMap name hash — feature and trap

By default, generators append a hash of the content to the name: checkout-config becomes checkout-config-9fh72tk4bd. Change a literal and the name changes, the name-reference transformer rewrites the Deployment’s reference, the pod spec therefore changes, and Kubernetes performs a rolling update. This is exactly what you want — a config change that actually reaches running pods, instead of a mutated ConfigMap that nobody re-reads until the next unrelated restart. It also has three consequences. Old hashed ConfigMaps are not garbage-collected by Kustomize, so they accumulate unless a pruning reconciler removes them. Setting disableNameSuffixHash: true restores the stale-config problem you were escaping. And an object referencing the ConfigMap in a way Kustomize can’t see — a name inside an annotation, a custom resource it has no schema for — will not be rewritten and will point at a name that no longer exists.

commonLabels and immutable selectors

The classic outage. commonLabels writes its labels into spec.selector.matchLabels as well as into pod templates. But a Deployment’s selector is immutable, so adding a common label to an app that is already running produces field is immutable on apply — an Argo CD sync that fails forever, or a stuck Flux reconciliation. Use the newer labels field with includeSelectors: false for anything cosmetic, and reserve selector-touching labels for greenfield objects. If you are already stuck, the fix is a delete-and-recreate of the Deployment (in Argo CD, the Replace=true sync option, adding Force=true if the replace is itself rejected — an in-place update of an immutable selector is refused whichever verb you use), with the downtime that implies — see Triage: Delivery.

Patch targets that silently match nothing

A strategic-merge patch that names a resource which does not exist fails loudly (no matches for Id ...). But a patches entry whose target uses a labelSelector or annotationSelector that matches zero objects is, in the general case, a silent no-op. Your build succeeds, the manifest is unchanged, and nobody notices until prod behaves like staging. This is why kustomize build | grep — or better, a golden-file test that diffs the rendered output in CI — belongs in every serious Kustomize repo.

Version skew and ordering surprises

The kustomize embedded in kubectl lags the standalone binary, sometimes by a lot, and Argo CD and Flux each bundle their own. A build that works on your laptop can fail in the cluster because that renderer predates the field you used. Pin deliberately, and prefer features that have been stable for a while. Ordering bites too: transformers run in a fixed sequence, so patches see pre-prefix names — which is why the examples above target checkout, not prod-checkout. And remote bases (resources: [github.com/org/repo//path?ref=v1.2.0]) are only reproducible if you pin ?ref= to a tag or SHA; without it, someone else’s main can change your prod render overnight.

🦆 Dot’s-eye view

“Honestly? I like that I can read it. With the chart we use for our other service I have to hold two files in my head — the template and the values — and mentally run the loop to guess what comes out. With Kustomize I just run kubectl kustomize overlays/dev and look at the actual YAML. When something’s wrong, it’s wrong in a file I can see, not inside a rendering step I can’t.”

Alternatives and when to choose it

☺ Like you’re 10: Kustomize is for your own apps in a few flavours. Helm is for shipping software to other people. You are allowed to use both.

The honest framing is not “which is better” but “what are you doing.” Are you distributing software to strangers who need a stable, documented set of knobs? That is a package, and packages want Helm. Are you operating your own workloads across environments you control? That is customisation, and customisation wants Kustomize.

DimensionKustomizeHelmRaw YAML per env
MechanismMerge/patch real objectsGo templating + valuesCopy-paste
Learning curveLow — it is just YAMLMedium — a template languageNone
ReachAny field of any objectOnly what the author parameterisedEverything, painfully
Packaging & distributionNone (git paths / OCI via others)Charts, repos, OCI registriesNone
Release history & rollbackNo — Git is your historyYes — helm history / rollbackNo
Lifecycle hooksNoYes (pre/post-install hooks)No
Install footprintBuilt into kubectlSeparate binaryNothing
Best atYour apps, several environmentsThird-party software, add-onsOne tiny app, one cluster

Combining them, on purpose

The most common mature setup uses both. A vendor ships a chart; you need one change the chart author never exposed. Two clean routes exist. Kustomize’s helmCharts field inflates the chart during the build (with --enable-helm), then applies your patches to the rendered output. Or you run helm template in CI, commit the rendered manifests, and treat them as a Kustomize base — the “rendered manifests” pattern described in Configuration Management, which also makes every diff reviewable. Either way, the rule of thumb is: Helm for what you consume, Kustomize for what you customise.

The wider field

Beyond these two sit jsonnet, cdk8s, Pulumi, and typed-configuration tools like CUE, KCL and Timoni, plus workload abstractions such as Score. They buy you real types, functions and validation at the cost of another language in the repo. For a platform team whose users are application developers, the boring answer is usually right: Helm for add-ons, Kustomize for services, and a self-service layer above so most developers never see either.

🦫 Benny’s workshop · 15 min

Make a folder with base/ containing a two-replica nginx Deployment, a Service, and a kustomization.yaml. Run kubectl kustomize base and read the output. Now add overlays/prod/ with namePrefix: prod-, replicas: [{name: nginx, count: 5}], and a configMapGenerator with one literal — and make the base container actually consume it, e.g. envFrom: [{configMapRef: {name: <generator name>}}], otherwise the name hash has no reference to rewrite and no rollout happens. Build it and note the hashed ConfigMap name. Apply it with kubectl apply -k overlays/prod, then change the literal, run kubectl diff -k overlays/prod, and watch it propose a new ConfigMap plus a changed pod template — the rollout trigger, live. Finally add commonLabels: {tier: web} to the base, re-apply, and meet field is immutable in person. Three minutes of pain now saves an afternoon later.

🎬 At the Platform Guild
🦊

Foxy: So Kustomize is basically Helm without the annoying curly braces?

🦫

Benny: Without the templating, which is different. Helm fills in blanks before the YAML exists. Kustomize patches YAML that already exists — so you can change fields nobody thought to expose.

🦆

Dot: And I can just look at the result. kubectl kustomize overlays/dev and there it is, no guessing.

👺

Gizmo: Ooh, I’ll pop a secretGenerator with the database password right in the overlay. It comes out base64! Practically a vault. 🤑

🐢

Timmy: Base64 is encoding, Gizmo. I decoded it while you were talking. Reference the secret, never the value.

🦋

Mira: Also — please don’t add commonLabels to the running checkout app. Deployment selectors are immutable and Argo CD will fail that sync until someone deletes the Deployment.

🦫

Benny: Use labels with includeSelectors: false. Same labels, no outage. That one line is worth the whole page.

Exam relevance and going further

☺ Like you’re 10: Kustomize isn’t on the tool list, but it’s inside kubectl and inside the tools that are — so learn it anyway, and learn it from memory.

Where it shows up

Kustomize does not appear on the official CNPE tool list, so no question will be titled “Kustomize.” It shows up sideways, and often: as the thing an Argo CD Application path points at, as spec.path in a Flux Kustomization, as the mechanism behind “deploy the same app to three environments” in a reference architecture question, and as kubectl apply -k in a performance task. Recognising a kustomization.yaml and predicting what it renders is a genuinely useful exam skill.

The doc-allowlist caveat

⚠ kustomize.io is not available in the exam

During the CNPE exam the only documentation you may open is kubernetes.io/docs, kubernetes.io/blog, task-specific links given in the exam’s Quick Reference box, and local man pages or /usr/share docs. Kustomize’s own site (kubectl.docs.kubernetes.io / the SIG repo) is not on that list. The good news: because Kustomize is part of kubectl, the kubernetes.io Declarative Management of Kubernetes Objects Using Kustomize task page is in scope, and it contains most field examples you would want. Beyond that, rely on kubectl kustomize --help and your memory — drill the shapes on Know Cold.

⚖ CNPA vs CNPE — That allowlist mechanic is entirely a CNPE thing — CNPE is hands-on, so it permits a narrow slice of live documentation lookup during the exam. CNPA is stricter, not looser: it is a fully closed-book multiple-choice exam with zero external resources and zero lookups of any kind, allowlisted or not. Even so, knowing Kustomize’s shapes cold is still worthwhile prep for CNPA’s closed-book recall.

What to be able to do without notes

Write a kustomization.yaml from a blank file: apiVersion: kustomize.config.k8s.io/v1beta1, kind: Kustomization, and a resources list. Add a base reference, a namespace, a namePrefix, an images entry with newName/newTag, and a replicas override. Write a strategic-merge patch as a partial object and a JSON 6902 patch with a target. Explain the ConfigMap name hash and why it causes a rollout. Say why commonLabels can break a running Deployment. And know the three commands cold: kubectl kustomize, kubectl diff -k, kubectl apply -k.

Official resources for after the exam

When you are not sitting the exam, the canonical sources are the Kustomize documentation at kubectl.docs.kubernetes.io (the Kustomization field reference is the page to bookmark), the Kubernetes task page at kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization — which is allowlisted — the project site at kustomize.io, and the source and examples at github.com/kubernetes-sigs/kustomize. Pair this page with Configuration Management for the wider landscape, Helm for the comparison, the tool landscape to see where it sits, and the glossary when a term stops making sense.

🐢 Timmy’s checkpoint

1. What does “template-free” actually mean, and what can Kustomize patch that Helm cannot? 2. You change one literal in a configMapGenerator and the pods restart. Why? 3. You add commonLabels to a live app and the sync fails with field is immutable. What happened, and what is the non-destructive fix going forward? 4. Your patches entry with a labelSelector target changes nothing and the build still succeeds. Why, and how do you catch it in CI? 5. Which two commands let you see the result before touching the cluster? 6. During the exam, where can you look up kustomization.yaml fields?

Check your answers
  1. Every input is ordinary, valid Kubernetes YAML — no placeholder syntax and no render step, just a merge. Because it patches real objects rather than filling in author-provided parameters, Kustomize can change any field of any object, including fields a chart author never exposed.
  2. The generator appends a content hash to the ConfigMap’s name, so a new literal means a new name; the name-reference transformer rewrites the Deployment’s reference to it, which changes the pod template and triggers a rolling update. It is deliberate — it guarantees config changes actually reach running pods. (Old hashed ConfigMaps are not garbage-collected, so you need a pruning reconciler.)
  3. commonLabels injects labels into spec.selector.matchLabels, and a Deployment’s selector is immutable. Going forward use the labels field with includeSelectors: false; to unstick the existing object you must delete and recreate it (in Argo CD, the Replace=true sync option, plus Force=true if the replace is also rejected), which is disruptive.
  4. A target selector matching zero objects is a silent no-op — unlike a named strategic-merge patch, which errors with no matches for Id. Catch it with a golden-file test: run kustomize build in CI and diff the rendered output against a committed expected file.
  5. kubectl kustomize <dir> (or kustomize build <dir>) prints the rendered manifests, and kubectl diff -k <dir> shows what would change in the cluster. Neither applies anything.
  6. Only on kubernetes.io/docs — the Declarative Management of Kubernetes Objects Using Kustomize task page — plus the Quick Reference links and local man//usr/share docs. kustomize.io and kubectl.docs.kubernetes.io are not on the allowlist, so memorise the shapes via Know Cold.