Kustomize: Native Kubernetes Configuration Management

Use Kustomize for declarative Kubernetes configuration management without Helm's templating—overlays, patches, and environment-specific customization.

published: reading time: 29 min read author: GeekWorkBench updated: June 17, 2026
Quick Summary

Kustomize handles Kubernetes configuration through overlays and patches rather than templates, so your YAML stays readable and diffable at every stage. It ships built into kubectl since 1.14, with strategic merge and JSON 6902 patches, ConfigMap/Secret generators, and components for sharing reusable configuration across teams. The post benchmarks Kustomize against Helm, surfaces real failure modes like name prefix collisions and deep overlay nesting, and closes with GitOps patterns that actually work in production.

Introduction

Managing Kubernetes manifests across multiple environments starts simple and gets unwieldy fast. Base config, dev overrides, staging overrides, prod overrides — copying and pasting YAML across all of that is a full-time job that nobody wants. Kustomize solves this without a templating engine or new programming language.

It is built into kubectl since 1.14, so nothing to install separately. You get a base with your canonical manifests, then overlays that only contain the differences for each environment. Look at any overlay and you see exactly what changed from base — no template rendering in your head required.

This post covers the basics: base and overlay structure, patches for modifying resources, generators for ConfigMaps and Secrets, and components for reusable blocks. You will also learn when Kustomize makes sense versus Helm, and where teams run into trouble managing configuration across large deployments.

When to Use / When Not to Use

When Kustomize makes sense

Kustomize is a good fit when your team owns the application manifests directly and wants environment-specific variations without introducing a new templating layer. If your dev, staging, and production configs differ in straightforward ways (replica counts, image tags, namespace names), overlays let you express those differences clearly without Go template syntax.

GitOps workflows benefit from Kustomize because the source YAML stays readable and diffable. You do not need to mentally render a template to understand what will be deployed.

For platform teams building reusable bases that other teams consume as a starting point, Kustomize components provide composition without publishing packages.

The specific configuration differences that map cleanly to Kustomize overlays are those that act on existing fields without conditional logic: replica counts (spec.replicas), image tags (spec.containers[*].image), namespace names, common labels and annotations, and resource limits. These are field-level overrides where the overlay declares the desired end state and Kustomize merges it onto the base. If your environment differences are limited to these shapes, overlays stay readable and the kustomization file serves as a clear manifest of what differs.

GitOps workflows benefit from Kustomize because the source YAML stays readable and diffable. You do not need to mentally render a template to understand what will be deployed. A reviewer can read the overlay kustomization.yaml and immediately see that production adds five replicas and flips the image tag to 1.21.1 — no template evaluation required. This transparency reduces the feedback cycle when auditing changes in pull requests.

Platform teams building a base for other teams to fork or import benefit from components. A platform team defines a component with standard monitoring sidecars, RBAC policies, and network labels once, then publishes the path to downstream teams. Those teams import the component into their overlays without needing to understand the internals. The composition is explicit in the kustomization.yaml, and the final YAML is deterministic — run kubectl kustomize and the same output appears every time.

When to choose Helm instead

If you need to distribute a reusable package to users who should not see the underlying template logic, Helm charts are better. The extensive ecosystem of Bitnami and public charts gives you off-the-shelf solutions for databases, caches, and middleware.

When your configuration varies in complex ways that do not map cleanly to overlays and patches, Helm templating offers more expressive power. Complex configuration means conditional logic: “if the user sets high.memory in values, add a memory limit of 16Gi and also inject a sidecar container.” That kind of conditional rendering does not express cleanly as a Kustomize overlay — you end up stacking multiple overlays or fighting the patch model. Helm’s Go template syntax handles that naturally.

Complex configuration that does not map to overlays is the clearest trigger for Helm. When your values file needs conditional logic — if high.memory is set, add a 16Gi memory limit and also inject a monitoring sidecar — Kustomize overlays start to break down. You end up stacking multiple overlays, each handling one conditional branch, or you fight the patch model to express what should be a simple if-then. Helm’s Go template syntax handles this directly: {{- if eq .Values.profile "high.memory" }} with the full conditional block inline. The rendered output is predictable and the logic lives in one place.

The distribution model is the second concrete trigger. If your platform team publishes a PostgreSQL chart for application teams to install, those teams should not need to understand how the chart templates the StatefulSet, the Service, and the ConfigMap together. They provide persistence.size: 100Gi and replication.enabled: true in a values file, and Helm renders the complete package. Kustomize has no equivalent to this — you share Git paths, not installable packages with versioned releases. A chart with a version number and a values schema is a distributable artifact. A set of kustomization overlays is a directory structure that other teams must copy and maintain themselves.

The Bitnami ecosystem is a concrete benefit that rarely appears in these comparisons. Bitnami maintains Helm charts for PostgreSQL, MySQL, Redis, RabbitMQ, Kafka, and dozens of other off-the-shelf infrastructure components. You get battle-tested manifests with upgrade paths, rollback support, and sane defaults without building anything yourself. If your project needs a message queue or a cache layer, helm install bitnami/redis is a solved problem. Kustomize has no registry of equivalent community-maintained overlays for common middleware.

Kustomize Workflow Flow

flowchart TD
    A[base/<br/>kustomization.yaml] --> B[Overlays]
    B --> C[development/<br/>kustomization.yaml]
    B --> D[staging/<br/>kustomization.yaml]
    B --> E[production/<br/>kustomization.yaml]
    C --> F[kubectl kustomize<br/>./overlays/development]
    D --> G[kubectl kustomize<br/>./overlays/staging]
    E --> H[kubectl kustomize<br/>./overlays/production]
    F --> I[Transformed<br/>YAML manifests]
    G --> I
    H --> I

Kustomize Overview and kubectl Integration

Kustomize is built into kubectl since Kubernetes 1.14, so you do not need separate installation. It reads kustomization.yaml files and produces transformed Kubernetes manifests.

# Basic usage with kubectl
kubectl apply -k ./overlays/production

# Build without applying
kubectl kustomize ./base

# View diff before applying
kubectl diff -k ./overlays/production

A simple Kustomize structure:

app/
├── base/
│   ├── kustomization.yaml
│   ├── deployment.yaml
│   └── service.yaml
└── overlays/
    ├── development/
    │   └── kustomization.yaml
    └── production/
        └── kustomization.yaml

Base and Overlay Structure

The base directory contains your canonical configuration. Overlays modify the base for specific environments.

Base kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml

commonLabels:
  app.kubernetes.io/part-of: myapp

images:
  - name: nginx
    newTag: "1.21"

Base deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: nginx
          image: nginx:1.21
          ports:
            - containerPort: 80

Development overlay:

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

bases:
  - ../../base

namespace: myapp-dev

namePrefix: dev-

commonLabels:
  environment: development

replicas:
  - name: myapp
    count: 1

images:
  - name: nginx
    newTag: "1.21-debug"

Production overlay:

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

bases:
  - ../../base

namespace: myapp-prod

namePrefix: prod-

commonLabels:
  environment: production

replicas:
  - name: myapp
    count: 5

images:
  - name: nginx
    newTag: "1.21.1"

Patches and Strategic Merge

Kustomize supports two patch strategies: Strategic Merge Patches (similar to kubectl patch) and JSON 6902 patches (RFC 6902 JSON Pointer).

Strategic merge patches:

# patches/replica-change.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 10
# kustomization.yaml
patches:
  - path: patches/replica-change.yaml

JSON 6902 patches for fine-grained control:

# patches/resource-limits.json
[{
  "op": "add",
  "path": "/spec/template/spec/containers/0/resources/limits/memory",
  "value": "512Mi"
}]

# kustomization.yaml
patches:
  - target:
      kind: Deployment
      name: myapp
    path: patches/resource-limits.json

Targeted patches:

# Patch only resources matching labels
patches:
  - patch: |-
      - op: replace
        path: /spec/replicas
        value: 3
    target:
      labelSelector: tier=frontend

Generators (ConfigMap and Secret)

Kustomize can generate ConfigMaps and Secrets from files, literals, or env files.

Literal-based ConfigMap:

# kustomization.yaml
configMapGenerator:
  - name: app-config
    literals:
      - DATABASE_HOST=localhost
      - DATABASE_PORT=5432
      - LOG_LEVEL=info

File-based ConfigMap:

# config/app.properties
database.url=jdbc:postgresql://localhost:5432/mydb
cache.enabled=true

# kustomization.yaml
configMapGenerator:
  - name: app-config
    files:
      - config/app.properties

Env file ConfigMap:

# envvars.txt
PORT=8080
WORKERS=4

# kustomization.yaml
configMapGenerator:
  - name: app-env
    envs:
      - envvars.txt

Generate Secret:

configMapGenerator:
  - name: app-secret
    literals:
      - api-key=$(API_KEY)
    envs:
      - secrets.txt

Kustomize Components for Reuse

Components allow reusable configuration blocks that can be imported across multiple kustomizations.

Define a monitoring component:

# components/monitoring/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha
kind: Component

resources:
  - deployment-metrics.yaml
  - service-monitor.yaml

patches:
  - patch: |-
      - op: add
        path: /spec/template/spec/containers/-
        value:
          name: prometheus
          image: prometheus:latest
          ports:
            - containerPort: 9090
    target:
      kind: Deployment

Use the component:

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

bases:
  - ../../base

components:
  - ../../components/monitoring

patches:
  - path: patches/production-config.yaml

Kustomize vs Helm Comparison

Both tools solve configuration management but with different philosophies:

AspectKustomizeHelm
ApproachOverlay/patchTemplate with values
Learning curveLower (no new syntax)Higher (Go templates)
FlexibilityLimited to kustomize featuresHighly flexible
DebuggingDirect YAML outputRendered templates
Secret managementBuilt-in generatorsExternal tools needed
EcosystemNative to KubernetesLarge chart repository
Best forApp-centric deploymentsOff-the-shelf packages

Choose Kustomize when you want direct control over your YAML and do not need to package complex application logic. Choose Helm when you want to distribute reusable packages or need the extensive ecosystem of public charts.

Production Failure Scenarios

Name prefix collisions across teams

If two teams both use namePrefix: dev- in their overlays and deploy to the same cluster, resources can collide. The dev-api Deployment from team A overwrites the dev-api Deployment from team B.

Use team-specific prefixes or deploy to isolated namespaces. The team-specific prefix pattern uses a two-part prefix: environment and team name. Instead of dev-, use dev-teamA- and dev-teamB-. This makes the collision impossible by construction — dev-teamA-api and dev-teamB-api are distinct resource names, even if both teams deploy to the same cluster and the same namespace. The naming convention lives in each team’s overlay kustomization.yaml as namePrefix: dev-teamA-.

Namespace isolation is the primary mitigation for teams that do not want to coordinate prefix naming. Each team gets its own namespace (team-a-dev, team-b-dev), and the overlay sets namespace: team-a-dev. Resources from team A and team B never share a namespace, so name collisions produce an API error rather than a silent overwrite. This approach scales better in organizations where teams do not communicate about naming conventions — the cluster API enforces isolation.

To audit for collisions before they happen, run kubectl kustomize for each overlay and grep for resource names. A CI script can extract all kind: Deployment names from each overlay’s output and compare them. If two overlays produce the same Deployment name in the same namespace, the script fails the pipeline before the apply. This check runs at the same time as the dry-run validation — it adds no additional steps to the CI workflow.

Patch targets wrong resources

Strategic merge patches apply to all resources matching the selector. If you have multiple Deployments with the same labels, a replica patch affects all of them simultaneously.

Always use precise target selectors:

patches:
  - patch: |-
      - op: replace
        path: /spec/replicas
        value: 10
    target:
      name: myapp
      kind: Deployment

ConfigMap generator creates new resources on every run

Kustomize generates ConfigMaps with content hashes in the name by default. Changing a ConfigMap literal creates a new ConfigMap with a different hash and deletes the old one. If a Deployment references the old ConfigMap name, the old ConfigMap cannot be deleted because the Deployment still needs it.

Pin ConfigMap generator outputs explicitly or use behavior: merge to update existing ConfigMaps rather than replacing them. The behavior field accepts three values: replace (the default, which deletes and recreates the ConfigMap on any content change), merge (which updates the existing ConfigMap in place and preserves its name), and retention (which keeps the old ConfigMap when a new one is generated, useful for zero-downtime transitions). Using behavior: merge is the correct choice when a Deployment references the ConfigMap by name — the Deployment keeps working while Kustomize updates the ConfigMap content in place.

The hash-based naming mechanism appends a content hash to the ConfigMap name. For example, a ConfigMap named app-config with literals LOG_LEVEL=info becomes app-config-7d8c9e5mh8. When LOG_LEVEL changes to debug, Kustomize generates app-config-3f6a1b9k2l and deletes the old one. This deterministic naming ensures that changing a ConfigMap triggers a rolling update on any Deployment that references it — the Deployment’s volume mount or environment variable reference to the old hash becomes invalid, forcing a new pod. This is the intended behavior for configuration changes that require pod restarts.

The Deployment reference problem surfaces when you use behavior: replace (the default) and the Deployment has already rolled out using the old ConfigMap hash. Kustomize deletes the old ConfigMap and creates a new one with a different hash, but the Deployment’s pod spec still references the old hash name. The new ConfigMap is created, but the Deployment cannot use it because its selector still points to the deleted resource. Using behavior: merge avoids this by keeping the ConfigMap name stable — the same name persists across content updates, so the Deployment’s reference remains valid throughout.

Overlay path not found

If you reference ../../base in an overlay and the path is wrong, Kustomize fails with a confusing error about a missing file. The error message does not clearly indicate it is a path resolution problem.

Use absolute paths or ensure your relative path references are correct before running in CI. The actual error message from Kustomize when the base path is wrong looks like this: Error: accumulating dirty './overlay': path '../base' does not exist. The word “accumulating dirty” (with a garbled character) is Kustomize’s internal term for building the resource list from multiple kustomization files, and the error does not say “cannot find base” — it says the file referenced by bases does not exist. This makes the error easy to misread as a general YAML syntax problem.

Relative paths are fragile in CI because the working directory differs from your local shell. Locally, you might run kubectl kustomize ./overlays/development from the repository root and ../../base resolves correctly. In a CI container, the working directory might be /github/workspace or a temp directory, and the same relative path breaks. The error surfaces only at apply time, after the pipeline has already committed to the build.

The absolute path solution sets bases: [/path/to/base] in the overlay kustomization.yaml. In a monorepo with a predictable layout, this resolves from any working directory. Alternatively, ensure your CI pipeline always runs kustomize from the repository root by setting workingDirectory in the pipeline config. A CI snippet that validates paths before running kustomize catches this class of error early:

# Validate base path exists before running kustomize
BASE_PATH="./base"
if [ ! -d "$BASE_PATH" ]; then
  echo "ERROR: base path $BASE_PATH does not exist"
  exit 1
fi
kubectl kustomize ./overlays/production

Common Pitfalls / Anti-Patterns

Overly deep directory nesting

Nesting overlays five levels deep — base, then environment, then region, then cluster, then tenant — makes it impossible to understand what the final manifest looks like without running kubectl kustomize. The further down the stack you go, the harder it becomes to trace a specific field back to its origin.

Two to three levels works in practice: one base, one environment overlay, and optionally one cluster or tenant overlay if you need both dimensions. Past that, the mental overhead of composing the final YAML outweighs any organizational gain.

For reuse across many overlays, use components instead of stacking more overlay layers. A component lives at the same level as base and gets imported into any overlay that needs it — one copy, no inheritance chain to trace. If you already have a deep stack, walk the directory tree and measure the longest chain. Refactoring the middle layers into components flattens the structure and makes each overlay readable on its own.

Duplicate resources across overlays

If base defines a resource and an overlay also defines the same resource without using patches or strategic merge, Kustomize produces duplicate resource errors. This is especially confusing because the error appears at apply time, not at kustomize build time.

Duplicate resources break the build in two stages. First, kubectl kustomize succeeds — it only assembles YAML, it does not validate uniqueness. The collision surfaces when kubectl apply reaches the cluster API server, which rejects two resources with the same kind and name. By then, the build pipeline has already committed to the manifests and may have pushed them to a deployment branch.

The fix is straightforward: audit your overlays regularly. A quick check runs kubectl kustomize and counts resource kinds, then compares that count against what you expect. An unexpected spike in Deployment or Service count usually means an overlay re-declared a resource that base already provides.

# Catch duplicate resources before apply
kubectl kustomize ./overlays/production > build/manifests.yaml
RESOURCE_COUNT=$(grep -c "^kind:" build/manifests.yaml)
EXPECTED_COUNT=12  # adjust for your setup
if [ "$RESOURCE_COUNT" -ne "$EXPECTED_COUNT" ]; then
  echo "WARNING: Expected $EXPECTED_COUNT resources, found $RESOURCE_COUNT"
fi

Run this in CI as part of your validation step alongside the dry-run check. It catches the class of error where someone adds a resource to an overlay without realizing base already provides it.

Not using —dry-run in CI

Deploying without testing the kustomize output first means you find out about problems after they happen in production. Always run kubectl diff -k or kubectl kustomize | kubectl apply --dry-run=server in CI before applying.

Without dry-run testing, failure is quiet until it hits production. A bad image tag stalls the Deployment at ImagePullBackOff. A malformed patch drops a field and the resource applies incomplete. A missing base reference breaks the build — but only if your CI actually runs kustomize before pushing; some pipelines skip that step entirely.

The minimum viable dry-run pipeline runs three commands:

# 1. Build and validate YAML syntax
kubectl kustomize ./overlays/production > build/manifests.yaml

# 2. Validate against cluster schema without applying
kubectl apply --dry-run=server -f build/manifests.yaml

# 3. Show what changed compared to what's running
kubectl diff -k ./overlays/production

kubectl diff -k is the most useful in pull requests. Reviewers see exactly which fields change, not just the kustomization diff, so regressions are visible before the merge. Without it, the deploy is opaque — you see what files changed, not what the cluster will actually do.

ArgoCD and Flux run their own dry-run when the controller syncs. That catches cluster-level problems. But you still want validation in CI so bad commits never reach the controller in the first place — a bad push triggering a sync in the middle of the night is avoidable with a local lint step.

Missing version pinning for kustomize

Kustomize versions differ in behavior. A kustomization that works with kustomize 3.8 may fail or produce different output with kustomize 5.0. Pin your kustomize version in CI and align it with your cluster’s kubectl version.

Version alignment matrix:

kubectl versionkustomize versionNotes
1.264.5.5+Align minor versions when possible
1.275.0.0+JSON 6902 patch improvements
1.28+5.1.0+Component API stable

Pin kustomize in CI:

# Install specific version in CI pipeline
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash -s -- 5.1.0 /usr/local/bin

# Confirm version matches what you expect
kustomize version

Detecting version drift: When CI succeeds but your local build fails, or overlays produce different output on different machines, the kustomize version is usually the culprit. Run kustomize version everywhere and compare the output.

kubectl integration note: kubectl 1.14 bundled kustomize as a built-in, which means kubectl kustomize uses whatever version ships with your kubectl binary. This caps what you can do with the built-in. If you need a newer kustomize than your kubectl includes, install it separately and use kustomize directly instead of kubectl kustomize.

Observability Hooks

Track the health and correctness of your Kustomize deployments with these practices.

What to monitor:

  • Kustomize build success/failure rate in CI
  • Time taken for kubectl kustomize to complete across environments
  • Number of resources generated per overlay (sudden changes indicate unintended patches)
  • YAML validation errors caught in CI vs at apply time

CI/CD observability:

# Example: Kustomize build step with error capture
- name: Build Kustomize manifests
  id: kustomize-build
  run: |
    mkdir -p build
    kubectl kustomize ./overlays/production > build/manifests.yaml
    RESOURCE_COUNT=$(kubectl kustomize ./overlays/production | kubectl apply --dry-run=server -f - 2>&1 | grep -c "created\|configured")
    echo "resources::$RESOURCE_COUNT"

    # Detect significant changes
    kubectl kustomize ./overlays/production | sha256sum > build/prod-checksum.txt
  continue-on-error: false

# Track diff size
- name: Check manifest diff
  run: |
    kubectl kustomize ./overlays/production > build/new.yaml
    DIFF_COUNT=$(diff -u build/base.yaml build/new.yaml | grep -c "^[+-]")
    echo "Changed lines: $DIFF_COUNT"
    if [ "$DIFF_COUNT" -gt 100 ]; then
      echo "WARNING: Large diff detected, verify intentional changes"
    fi

Debugging commands:

# View all generated resources
kubectl kustomize ./overlays/production

# Validate against cluster without applying
kubectl apply --dry-run=server -k ./overlays/production

# Check what will change (diff mode)
kubectl diff -k ./overlays/production

# Count resources generated
kubectl kustomize ./overlays/production | kubectl apply --dry-run=server -f - 2>&1 | grep -c "resource"

# Validate YAML syntax
kubectl kustomize ./overlays/production | kubectl validate --ignore-missing-schema

# Check for deprecated APIs
kubectl kustomize ./overlays/production | kubectl-convert --dry-run=client -o yaml | head -20

Alert on deployment anomalies:

# Alert if a deployment has significantly more/fewer resources than expected
- alert: KustomizeResourceCountAnomaly
  expr: |
    (count(kustomize_generated_resources{env="production"}) by (app)
    / avg(count(kustomize_generated_resources{env="production"}) by (app))) > 1.5
  labels:
    severity: warning
  annotations:
    summary: "Kustomize resource count anomaly for {{ $labels.app }}"
    description: "Production overlay generating {{ $value }}x more resources than average. Verify intentional changes."

# Alert if kustomize builds fail repeatedly in CI
- alert: KustomizeBuildFailures
  expr: increase(kustomize_build_errors_total[10m]) > 3
  labels:
    severity: critical
  annotations:
    summary: "Multiple kustomize build failures detected"
    description: "{{ $value }} kustomize build failures in the last 10 minutes. Check CI logs."

Trade-off Summary

AspectKustomizeHelmplain YAML
TemplatingNo (patches/overlays)Yes (Go templates)No
ReadabilityHigh (plain YAML)Medium (templates)Highest
ReusabilityGit-basedChart reposNone
Secret handlingSealed secrets + generatorshelm-secretsManual
GitOps integrationNative kubectlFlux/ArgoCDFlux/ArgoCD
DebuggingDiff is clearRender firstPlain kubectl
EcosystemGrowingMassive (Bitnami)N/A

Interview Questions

1. How does Kustomize's overlay approach differ from Helm's templating approach?

Expected answer points:

  • Kustomize uses overlays and patches—base YAML stays readable and diffable without rendering templates
  • Helm uses Go templates with values files—produces rendered templates but adds a layer of indirection
  • Kustomize keeps YAML human-readable at every stage; Helm requires mental template rendering to understand final output
  • Kustomize is native to kubectl (1.14+); Helm is a separate CLI with its own ecosystem of charts
2. What is the difference between strategic merge patches and JSON 6902 patches in Kustomize?

Expected answer points:

  • Strategic merge patches resemble kubectl patch syntax and work well for common fields like replicas, images, and labels
  • JSON 6902 patches (RFC 6902) use JSON Pointer notation for fine-grained control over any JSON path
  • Strategic merge does not support array element targeting by index; JSON 6902 can add/replace/remove specific array elements
  • JSON 6902 patches are more expressive but less readable for simple modifications
3. What are Kustomize components and when should you use them?

Expected answer points:

  • Components (kustomize.config.k8s.io/v1alpha1) are reusable configuration blocks importable across multiple kustomizations
  • They enable composition without publishing packages—unlike Helm charts, you reference local paths
  • Use cases: shared monitoring sidecar, common secret rotation logic, standardized networking policies
  • Components solve the reuse problem for platform teams building bases for other teams to consume
4. How does the ConfigMap generator's behavior differ from manually defining ConfigMaps?

Expected answer points:

  • Kustomize generates ConfigMaps with content hashes in the name by default—changing a literal creates a new ConfigMap and deletes the old
  • Manual ConfigMaps have static names; Kustomize's generator produces deterministic but different names when content changes
  • Use `behavior: merge` to update existing ConfigMaps instead of replacing them to avoid Deployment reference issues
  • The hash-based naming ensures rolled Deployments pick up updated ConfigMaps automatically
5. What is name prefix collision and how do you prevent it?

Expected answer points:

  • When two teams both use `namePrefix: dev-` in overlays and deploy to the same cluster, resources collide (dev-api from team A overwrites dev-api from team B)
  • Prevention options: use team-specific prefixes (dev-teamA-, dev-teamB-), deploy to isolated namespaces, or use organizational naming conventions
  • Name collisions are particularly dangerous in shared clusters with multiple teams
6. How do you debug what a kustomize overlay will produce before applying it?

Expected answer points:

  • `kubectl kustomize ./overlay` builds and prints the generated YAML without applying
  • `kubectl diff -k ./overlay` compares the generated manifests against what's currently running in the cluster
  • `kubectl apply --dry-run=server -k ./overlay` validates against the cluster schema without making changes
  • Always run these in CI before applying to catch issues early
7. What are the limitations of Kustomize compared to Helm?

Expected answer points:

  • Kustomize has no package distribution system—you share via Git, not chart repositories
  • No built-in templating logic for complex conditional rendering (Helm's Go templates are more expressive)
  • Secret management requires external tools (Sealed Secrets, external-secrets) whereas Helm has helm-secrets
  • Ecosystem is smaller—Bitnami and other public Helm charts do not have Kustomize equivalents for every off-the-shelf software
8. When should you choose Kustomize over Helm for a Kubernetes project?

Expected answer points:

  • Choose Kustomize when your team owns application manifests directly and wants readable, diffable YAML
  • Good fit for GitOps workflows where plain YAML in source control matters more than package distribution
  • Choose Helm when you need to distribute reusable packages to users who should not see template logic
  • Choose Helm when your configuration varies in complex ways that do not map cleanly to overlays and patches
9. What does the `commonLabels` field do in Kustomize and why is it useful?

Expected answer points:

  • `commonLabels` adds labels to all resources created from a kustomization—every Deployment, Service, ConfigMap gets the same labels
  • Use case: standardized labeling across environments (app name, version, environment, team)
  • Works with `namePrefix` to create environment-specific resource names while maintaining label consistency
  • Critical for tools that select resources by labels (monitoring, network policies, RBAC)
10. What is the recommended directory structure for a Kustomize project and why?

Expected answer points:

  • Typical structure: `app/base/` with kustomization.yaml + resource YAMLs, then `app/overlays/development/`, `app/overlays/production/`
  • Base contains canonical configuration; overlays reference base via `bases: [../../base]`
  • Keeps nesting shallow (2-3 levels max) to keep final YAML understandable
  • Components live at `app/components//` and are imported into overlays for reuse
11. How do you handle secret values in Kustomize without storing them in Git?

Expected answer points:

  • Use `kustomize edit create secret` to generate encrypted or reference-based secrets
  • Store sealed secrets or reference values only — not plaintext secrets in Git
  • Use External Secrets Operator to pull secrets from AWS Secrets Manager, HashiCorp Vault, or similar
  • Sealed Secrets: encrypt Kubernetes secrets with a public key; only sealed-secrets controller can decrypt
  • Use `.gitignore` to exclude `secrets.yaml` — only commit `secrets.yaml.s-example` or template
  • CDK8s or other tools can generate secrets from external sources during build
12. What is the difference between `bases` and `components` in Kustomize?

Expected answer points:

  • `bases` reference other kustomization directories — creates hierarchical composition (base → overlay)
  • `components` (kustomize.config.k8s.io/v1alpha1) are reusable configuration blocks importable into any kustomization
  • Components can be used in multiple overlays without hierarchical inheritance
  • Use components for: monitoring sidecar, secret rotation logic, network policies shared across teams
  • Use bases/overlays for: environment-specific differences (dev has 1 replica, prod has 5)
  • Components solve the reuse problem where hierarchical inheritance doesn't fit cleanly
13. How do you debug Kustomize overlays that produce unexpected results?

Expected answer points:

  • `kubectl kustomize ./overlay` prints generated YAML without applying — first step for debugging
  • `kubectl diff -k ./overlay` shows differences between current cluster state and overlay
  • Use `--load-restrictor=LoadRestrictionsNone` if bases are outside the kustomization directory
  • Check for duplicate resources: `kubectl kustomize | grep -c "kind:"` — high count may indicate duplicates
  • Validate with `kubectl apply --dry-run=server -k ./overlay` — catches schema validation errors
  • Use `kustomize build --enable-alpha-plugins` for debugging plugin-based generators
14. How does Kustomize handle patches when the same resource is defined in both base and overlay?

Expected answer points:

  • Strategic merge patch (SMP) merges fields: overlay values override base values for matching fields
  • If overlay's patch matches resource name/kind in base, fields merge; otherwise new resource created
  • JSON 6902 patches (RFC 6902) use JSON Pointer to target specific array elements or nested paths
  • If both patches target same field, the later patch in kustomization.yaml order wins
  • For conflicting patches: use `patchesStrategicMerge` with explicit targeting by name and kind
  • Debug: run `kubectl kustomize` and check if resulting YAML matches expectation
15. What is the purpose of the `commonLabels` and `commonAnnotations` fields in Kustomize?

Expected answer points:

  • `commonLabels` adds labels to ALL resources in the kustomization — Deployments, Services, ConfigMaps, etc.
  • Use for: environment label, team label, version label applied consistently across all resources
  • Works with `namePrefix` to create environment-specific names while keeping labels consistent
  • `commonAnnotations` adds annotations to all resources — useful for tool metadata, ownership
  • Labels are critical for selectors, RBAC, and network policies that match resources by label
  • Without commonLabels, you would need to add labels manually to each resource in base
16. How do you test Kustomize overlays in CI/CD before applying to a cluster?

Expected answer points:

  • `kubectl kustomize ./overlay` validates YAML syntax and generates final manifests
  • `kubectl diff -k ./overlay` compares generated manifests against currently deployed state
  • `kubectl apply --dry-run=server -k ./overlay` validates against cluster API schema
  • Run in CI before any apply: fail pipeline if diff shows unexpected changes
  • Use kubeval or conftest to validate generated YAML against custom policies
  • Count resources: compare `kubectl kustomize | grep -c "kind:"` between overlays to detect unexpected changes
17. What is the behavior of `replicas` field in Kustomize and when does it override the base?

Expected answer points:

  • `replicas` field in kustomization.yaml uses SMP to replace `spec.replicas` in matching Deployment
  • Target by name: `replicas: - name: myapp\n count: 5` sets myapp Deployment replicas to 5
  • If base has `replicas: 3` and overlay sets `replicas: 5`, overlay wins for matching name
  • If overlay defines same Deployment resource directly (not via patch), causes duplicate resource error
  • For multiple Deployments: can target each by name with different replica counts
  • Verify with `kubectl kustomize ./overlay | grep -A2 "replicas"` to check final values
18. How do you handle environment-specific configurations like feature flags in Kustomize?

Expected answer points:

  • Store feature flags in ConfigMap, patch different ConfigMap in each overlay
  • Overlay creates env-specific ConfigMap with `configMapGenerator` and references it in patches
  • Use `patchesJson6902` to replace specific values in ConfigMap without full replacement
  • Inject via environment variable: `env` field in container spec pulls from ConfigMap
  • Alternatively: use sealed secrets for feature flag values, overlay contains decryption key
  • Keep feature flag logic in ConfigMap, not hardcoded in application template
19. What is the recommended approach for managing multi-tenant clusters with Kustomize?

Expected answer points:

  • Use namespace-level isolation: each tenant gets own namespace with network policies
  • Base kustomization includes namespace, resource quotas, RBAC for tenant
  • Overlays per cluster/environment inherit from tenant base — not single global base
  • Use `namePrefix` per tenant to avoid resource name collisions across tenants
  • Platform team provides base with security controls; tenant teams customize workload overlay
  • RBAC: ServiceAccount per tenant, bound to tenant namespace only — prevents cross-tenant access
20. How does Kustomize compare to Helm for GitOps workflows with ArgoCD or Flux?

Expected answer points:

  • Both ArgoCD and Flux support both Kustomize and Helm — choice depends on team preference
  • Kustomize: YAML is Git-native, diffs are readable, no template rendering step
  • Helm: larger ecosystem of charts, more expressive templating for complex configurations
  • ArgoCD with Kustomize: Application set points to Git repo, ArgoCD syncs manifest generated by `kubectl kustomize`
  • ArgoCD with Helm: Application references chart repository or local chart, ArgoCD renders with `helm template`
  • Flux with Kustomize: Source controller pulls Git, Kustomize controller generates manifests
  • For teams owning their manifests: Kustomize's Git-native approach fits naturally
  • For teams consuming off-the-shelf software: Helm's chart ecosystem reduces work

Further Reading

Conclusion

Key Takeaways

  • Kustomize uses overlays and patches instead of templates, keeping YAML readable and diffable
  • The built-in kubectl kustomize requires no separate installation (Kubernetes 1.14+)
  • Name prefixes and common labels help standardize resources across environments
  • Components provide reusable configuration blocks without publishing packages
  • Choose Kustomize for app-centric GitOps; choose Helm for distributable packages

Kustomize Checklist

# Test kustomize output without applying
kubectl kustomize ./overlays/production

# Diff against cluster state
kubectl diff -k ./overlays/production

# Apply with dry-run to catch errors
kubectl apply -k ./overlays/production --dry-run=server

# Validate build in CI
kustomize build ./overlays/production | kubeval --strict

Category

Related Posts

GitOps: Declarative Deployments with ArgoCD and Flux

Implement GitOps for declarative, auditable infrastructure and application deployments using ArgoCD or Flux as your deployment operator.

#gitops #argocd #flux

ConfigMaps and Secrets: Managing Application Configuration in Kubernetes

Inject configuration data and sensitive information into Kubernetes pods using ConfigMaps and Secrets. Learn about mounting strategies, environment variables, and security best practices.

#kubernetes #configuration #secrets

GitOps: Infrastructure as Code with Git for Microservices

Discover GitOps principles and practices for managing microservices infrastructure using Git as the single source of truth.

#microservices #gitops #infrastructure-as-code