# kustomize Cheat Sheet

> **Tool:** kustomize
> **Category:** Containers & Orchestration
> **Verified against:** kustomize v5.8.1 (standalone), flags verified via `kustomize --help` / `kustomize
> build --help` / `kustomize edit --help` / `kustomize edit add --help` run locally, plus a real
> `kustomize create` → `edit add base` → `edit set image` → `edit set replicas` → `build` sequence executed
> against a local test kustomization directory, 2026-08-29.
> **Official docs:** https://kubectl.docs.kubernetes.io/references/kustomize/

## What it is and where it fits 🎯

kustomize customizes plain Kubernetes YAML **without a templating language** — no `{{ }}` placeholders, no
values file, no Go template syntax to learn. Instead you write a `kustomization.yaml` that points at a set
of real, valid manifests (a "base") and layers declarative patches, generators, and field overrides
("overlays") on top of them. `kubectl` itself embeds an older kustomize under `kubectl apply -k`, but the
standalone `kustomize` binary this page covers moves faster and is worth installing separately — this
sandbox's `kubectl` (v1.34.0) embeds kustomize **v5.7.1**, while the standalone binary installed fresh today
is already **v5.8.1**; kubectl's bundled version reliably lags the latest release. The natural point of
comparison is Helm: Helm is a templating *and* packaging system with versioned releases and a templating
language for genuinely variable logic (loops, conditionals); kustomize is patch-based composition for
environment-to-environment variation on manifests that are otherwise the same YAML you'd hand-write anyway.
Many GitOps setups (Argo CD, Flux) support both natively, and plenty of teams use Helm to *package* an app
and a kustomize overlay on top of the rendered chart output for last-mile, environment-specific tweaks.

## How a base and overlays combine

```mermaid
flowchart TD
    B["base/\ndeployment.yaml + kustomization.yaml"] --> O1["overlays/staging/\nkustomization.yaml"]
    B --> O2["overlays/production/\nkustomization.yaml"]
    O1 --> BS["kustomize build overlays/staging"]
    O2 --> BP["kustomize build overlays/production"]
    BS --> M1["Final staging manifest:\n1 replica, staging image tag"]
    BP --> M2["Final production manifest:\n3 replicas, pinned image, extra labels"]

    classDef info fill:#e5f0fa,stroke:#1d6fb8,color:#10161c
    classDef accent fill:#f0e9fb,stroke:#6d43c0,color:#10161c
    classDef ok fill:#e5f5ea,stroke:#1f8a4c,color:#10161c
    class B info
    class O1,O2 accent
    class M1,M2 ok
```

**The base never changes between environments** — every overlay references the same base manifests and
only *adds* patches on top, which is what keeps staging and production from drifting apart on anything the
overlays don't deliberately override.

## Installation

```bash
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
sudo mv kustomize /usr/local/bin/
brew install kustomize                                # macOS/Linux Homebrew
go install sigs.k8s.io/kustomize/kustomize/v5@latest    # if you have a Go toolchain

kustomize version
kubectl version --client -o yaml | grep -i kustomize    # check what version kubectl -k is actually running
```

> [!NOTE]
> The install script always fetches the latest release — if you need to pin a specific version for
> reproducibility, download the matching tarball directly from the
> [kustomize releases page](https://github.com/kubernetes-sigs/kustomize/releases) instead.

## Building a base

```bash
mkdir -p base && cd base
# deployment.yaml — a normal, complete, valid Kubernetes manifest, nothing kustomize-specific in it
kustomize create --resources deployment.yaml,service.yaml
```

`kustomize create` writes the starter `kustomization.yaml` for you — a base is genuinely just "a directory
with a `kustomization.yaml` listing which real manifests it owns," not a special file format.

## Creating an overlay

Real commands run in this sandbox against a base with one `Deployment` (`catalog-api`), producing a real
`overlays/production/kustomization.yaml`:

```bash
mkdir -p overlays/production && cd overlays/production
kustomize create --resources ../../base
kustomize edit add label "env:production"
kustomize edit set image registry.example.com/catalog-api=registry.example.com/catalog-api:1.4.2
kustomize edit set replicas catalog-api=3
```

Resulting file (captured verbatim from that run):

```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
images:
- name: registry.example.com/catalog-api
  newName: registry.example.com/catalog-api
  newTag: 1.4.2
replicas:
- count: 3
  name: catalog-api
labels:
- includeSelectors: true
  pairs:
    env: production
```

## Sample output — a real `kustomize build` run

Running `kustomize build overlays/production` against the files above (real captured output, not
illustrative):

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    env: production
  name: catalog-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: catalog-api
      env: production
  template:
    metadata:
      labels:
        app: catalog-api
        env: production
    spec:
      containers:
      - image: registry.example.com/catalog-api:1.4.2
        name: catalog-api
        ports:
        - containerPort: 8080
```

Every override applied cleanly: the image tag, the replica count, and the `env: production` label —
including on the Deployment's own `selector.matchLabels`, which `includeSelectors: true` deliberately
propagates into so the label actually participates in Pod selection, not just metadata.

## Patches: strategic merge vs. JSON 6902

```bash
kustomize edit add patch --path memory-patch.yaml --kind Deployment --name catalog-api    # strategic-merge-style patch file
kustomize edit add patch --path add-toleration.json --kind Deployment --name catalog-api --patch-type json    # RFC 6902 JSON patch
```

```yaml
# memory-patch.yaml — a strategic merge patch: only the fields you name are touched
apiVersion: apps/v1
kind: Deployment
metadata:
  name: catalog-api
spec:
  template:
    spec:
      containers:
        - name: catalog-api
          resources:
            limits:
              memory: 512Mi
```

```json
[
  { "op": "add", "path": "/spec/template/spec/tolerations", "value": [{ "key": "spot", "operator": "Exists" }] }
]
```

> [!IMPORTANT]
> **`patchesStrategicMerge` and `patchesJson6902` are deprecated in favor of a single unified `patches:`
> field** — confirmed live: running `kustomize edit fix` against a kustomization still using the old field
> names rewrote them automatically, printing `'commonLabels' is deprecated. Please use 'labels' instead` and
> folding both old patch fields into one `patches:` list. A kustomization authored a couple of years ago
> that still uses the old field names still works today, but new kustomizations should use `patches:` (with
> `target:` selecting the resource, and either inline YAML or `patch: |` strategic-merge content, or
> `path:`) from the start.

## Generators: ConfigMaps and Secrets from real files

```bash
kustomize edit add configmap app-config --from-literal=LOG_LEVEL=info --from-file=app.properties
kustomize edit add secret db-creds --from-literal=DB_PASSWORD=changeme --disable-name-suffix-hash
```

```yaml
configMapGenerator:
  - name: app-config
    literals:
      - LOG_LEVEL=info
    files:
      - app.properties
secretGenerator:
  - name: db-creds
    literals:
      - DB_PASSWORD=changeme
    options:
      disableNameSuffixHash: true
```

> [!WARNING]
> **By default, generated ConfigMaps/Secrets get a content hash suffix appended to their name**
> (`app-config-8f7d9c2b`), and kustomize automatically rewrites every reference to that name across the
> other manifests it builds. This is a deliberate feature — it forces a new Pod rollout whenever the
> ConfigMap's content changes, since Kubernetes itself doesn't restart Pods on a ConfigMap edit alone. Using
> `--disable-name-suffix-hash` (or a hardcoded name a Deployment references directly) trades that automatic
> rollout-on-change behavior away — only do it deliberately, for something like a Secret an external
> operator manages by fixed name.

## Config file format — `kustomization.yaml` field reference

| Field | Purpose |
|---|---|
| `resources` | Manifest files, directories, or a base path to include |
| `patches` | Strategic-merge or JSON 6902 patches, targeted by `target:` selectors |
| `images` | Override an image's name/tag/digest across every resource that references it |
| `replicas` | Override a workload's replica count by resource name |
| `labels` / `commonAnnotations` | Add labels/annotations to every generated resource (`labels` replaces the deprecated `commonLabels`) |
| `namePrefix` / `nameSuffix` | Prepend/append a string to every resource's `metadata.name` |
| `namespace` | Force every resource into one namespace, overriding whatever the base set |
| `configMapGenerator` / `secretGenerator` | Generate ConfigMaps/Secrets from literals or files, with automatic hash-suffix rollout behavior |
| `components` | Reusable, optionally-included chunks of kustomization logic (like a mixin) |
| `vars` (legacy) / `replacements` | Copy a field's value from one resource into another at build time — `replacements` is the current, more explicit mechanism |

## Real-world scenario: promoting a change from staging to production

A team keeps `overlays/staging/` and `overlays/production/` as siblings sharing one `base/` — promoting a
change means editing the base once and reviewing what each overlay's *build output* actually changes,
rather than copy-pasting YAML between environment folders:

```bash
kustomize build overlays/staging > /tmp/staging-before.yaml
# ... edit base/deployment.yaml ...
kustomize build overlays/staging > /tmp/staging-after.yaml
diff /tmp/staging-before.yaml /tmp/staging-after.yaml    # confirm exactly what changed, before it ships
kustomize build overlays/production | kubectl apply -f -
```

## Real-world scenario: GitOps with Argo CD or Flux, no CI render step at all

Both Argo CD and Flux understand `kustomization.yaml` natively — pointing an Argo CD `Application` or a Flux
`Kustomization` resource at `overlays/production/` means the GitOps controller runs the equivalent of
`kustomize build` itself on every reconciliation loop, continuously, without a CI pipeline step rendering
and pushing manifests anywhere. This is the single biggest reason teams standardize on kustomize's
patch-based model over a bespoke templating pipeline — the exact same directory a human runs `kustomize
build` against locally is what the GitOps controller reconciles against in the cluster.

## Real-world scenario: pinning every image in a PR without hand-editing YAML

A release pipeline needs to bump every service's image tag to a freshly-built SHA without touching the
actual Deployment manifests, so a reviewer's diff shows exactly one line per service:

```bash
cd overlays/production
kustomize edit set image catalog-api=registry.example.com/catalog-api@sha256:abc123...
git diff kustomization.yaml    # a one-line diff, not a YAML rewrite
```

## CI/CD integration: render-and-diff in a pull request

```yaml
# .github/workflows/kustomize-preview.yml
name: Kustomize preview
on: [pull_request]
jobs:
  render:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install kustomize
        run: |
          curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
          sudo mv kustomize /usr/local/bin/
      - name: Render production overlay
        run: kustomize build overlays/production > rendered.yaml
      - name: Upload rendered manifest as an artifact
        uses: actions/upload-artifact@v4
        with:
          name: rendered-manifests
          path: rendered.yaml
```

> [!TIP]
> Uploading the rendered output as a build artifact (rather than just running `build` and discarding the
> result) gives reviewers something concrete to diff against the previous merge's rendered output — catching
> an unintended change to a resource nobody meant to touch, which a diff of the *source* `kustomization.yaml`
> alone can't show.

## Common pitfalls

- **Trusting `kubectl apply -k` to match the standalone `kustomize` CLI's behavior exactly** — see the
  version-lag note in "What it is and where it fits"; a feature or bug fix in a newer standalone release
  may not exist yet in kubectl's embedded copy.
- **Still using `patchesStrategicMerge`/`patchesJson6902`/`commonLabels`** — all deprecated in favor of
  `patches:`/`labels:`; run `kustomize edit fix` to migrate a kustomization automatically.
- **Not realizing generated ConfigMap/Secret names get a hash suffix by default** — see the WARNING above;
  this breaks anything hardcoding the un-suffixed name.
- **A `replicas`/`images`/patch `target` name that doesn't match any actual resource** — `kustomize build`
  fails outright with an error naming the mismatched GVK/name rather than silently no-op'ing, which is easy
  to misread as a real bug when it's really just a typo in the overlay.
- **Forgetting `resources:` in an overlay must point at the base directory, not individual base files** —
  omitting the base entirely produces a build with only the overlay's own generators/patches and nothing to
  apply them to.

## Exit codes and when to reach for something else

`0` on a successful build/edit; non-zero (with a descriptive error naming the missing resource/GVK/field)
on anything that fails to resolve.

Reach for Helm instead when the actual variation between deployments needs real logic — conditionals, loops
over a list, computed values, or a chart meant for other teams/the public to consume with their own
`values.yaml`. Reach for kustomize when every environment is fundamentally the same manifests with a bounded
set of field-level differences, and when the team wants to review plain YAML in pull requests rather than a
templating language. The two aren't mutually exclusive — `kustomize build --enable-helm` can even inflate a
Helm chart as one more resource for kustomize to patch, for teams standardizing on kustomize as the outer
layer regardless of what a given component ships as internally.
