Skip to content

Kustomize — Native Kubernetes Configuration Management Guide

DodaTech Updated 2026-06-24 5 min read

In this tutorial, you'll learn about Kustomize. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Kustomize is a Kubernetes Configuration Management tool that lets you customize raw YAML files using overlays, patches, and generators — without templates or domain-specific languages.

What You'll Learn

Why It Matters

Managing environment-specific Kubernetes YAML by copying and modifying files leads to massive duplication and configuration drift. Kustomize uses a base-plus-overlay pattern: you maintain one set of base manifests and layer environment-specific changes on top. DodaTech uses Kustomize across 60+ Microservices, reducing YAML duplication by 80% and making environment-specific changes explicit and reviewable.

Real-World Use

DodaZIP's deployment repository has a base k8s/base/ directory with canonical manifests for each service. Environment overlays (k8s/overlays/dev/, k8s/overlays/production/) add only the differences — different replica counts, resource limits, ConfigMap values, and Ingress hosts.

flowchart TD
    A[k8s/base] --> B[deployment.yaml]
    A --> C[service.yaml]
    A --> D[configmap.yaml]
    B --> E[k8s/overlays/production]
    C --> E
    D --> E
    E --> F[replicas-patch.yaml]
    E --> G[resources-patch.yaml]
    E --> H[ingress.yaml]
    E --> I[kustomization.yaml]
    I --> J[kubectl apply -k]
    J --> K[Merged Production YAML]
    style E fill:#326CE5,color:#fff
    style J fill:#326CE5,color:#fff
â„šī¸ Info

Prerequisites: Basic Kubernetes knowledge and kubectl (v1.24+ includes Kustomize). Familiarity with YAML syntax.

Base Configuration

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

resources:
  - deployment.yaml
  - service.yaml
  - configmap.yaml

commonLabels:
  app.kubernetes.io/managed-by: kustomize
  app.kubernetes.io/part-of: dodazip

namePrefix: dodazip-

configMapGenerator:
  - name: app-config
    literals:
      - NODE_ENV=production
      - LOG_LEVEL=info
      - API_URL=https://api.dodatech.com
    behavior: create
# k8s/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: registry.dodatech.com/api:latest
          ports:
            - containerPort: 8080
          envFrom:
            - configMapRef:
                name: app-config
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
# k8s/base/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 8080

Overlay Configuration

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

resources:
  - ../../base

patches:
  - path: replicas-patch.yaml
    target:
      kind: Deployment
      name: api
  - path: resources-patch.yaml
    target:
      kind: Deployment
      name: api
  - path: hpa.yaml

configMapGenerator:
  - name: app-config
    behavior: merge
    literals:
      - LOG_LEVEL=warn
      - API_URL=https://api.dodatech.com
      - REDIS_URL=redis://redis-cluster.production:6379

images:
  - name: registry.dodatech.com/api
    newTag: v2.5.0

replicas:
  - name: api
    count: 5

commonLabels:
  environment: production
# k8s/overlays/production/replicas-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 5
# k8s/overlays/production/resources-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  template:
    spec:
      containers:
        - name: api
          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: 2000m
              memory: 1024Mi
# k8s/overlays/production/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

Development Overlay

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

resources:
  - ../../base

patches:
  - path: replicas-patch.yaml

configMapGenerator:
  - name: app-config
    behavior: merge
    literals:
      - LOG_LEVEL=debug
      - API_URL=http://localhost:8080

images:
  - name: registry.dodatech.com/api
    newTag: latest

replicas:
  - name: api
    count: 1

commonLabels:
  environment: dev

namespace: dev
# k8s/overlays/dev/replicas-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 1

Strategic Merge and JSON Patches

# patches/ingress-patch.yaml — Strategic merge patch
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - api.dodatech.com
      secretName: dodatech-tls
  rules:
    - host: api.dodatech.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 80
# patches/json-patch.yaml — JSON 6902 patch
- op: add
  path: /spec/template/spec/containers/0/env/-
  value:
    name: NEW_RELIC_LICENSE_KEY
    valueFrom:
      secretKeyRef:
        name: newrelic-keys
        key: license_key

- op: replace
  path: /spec/replicas
  value: 10

- op: add
  path: /metadata/annotations
  value:
    reloader.stakater.com/match: "true"

Using Kustomize

# Build the production overlay (dry run)
kubectl kustomize k8s/overlays/production

# Expected output:
# apiVersion: v1
# kind: ConfigMap
# metadata:
#   name: dodazip-app-config-4k5m2h8g3f
# data:
#   API_URL: https://api.dodatech.com
#   LOG_LEVEL: warn
#   NODE_ENV: production
#   REDIS_URL: redis://redis-cluster.production:6379
# ---
# apiVersion: apps/v1
# kind: Deployment
# metadata:
#   labels:
#     app.kubernetes.io/managed-by: kustomize
#     app.kubernetes.io/part-of: dodazip
#   name: dodazip-api
# spec:
#   replicas: 5
# ...

# Apply to cluster
kubectl apply -k k8s/overlays/production

# Expected output:
# configmap/dodazip-app-config-4k5m2h8g3f created
# deployment.apps/dodazip-api created
# service/dodazip-api created

# Diff between overlays
kubectl diff -k k8s/overlays/dev -k k8s/overlays/production

# Delete everything built from an overlay
kubectl delete -k k8s/overlays/production

Generators and Transformers

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

resources:
  - deployment.yaml

secretGenerator:
  - name: app-secrets
    envs:
      - .env.secret
    type: Opaque

configMapGenerator:
  - name: app-config
    files:
      - configs/app.properties
      - configs/logback.xml

generatorOptions:
  disableNameSuffixHash: false
  labels:
    type: generated
  annotations:
    note: generated-by-kustomize

patchesStrategicMerge:
  - patches/tolerations.yaml

namespace: production

namePrefix: prod-
nameSuffix: -v2

commonAnnotations:
  deployed-by: argo-cd
  environment: production

Common Configuration Mistakes

  1. Not using namePrefix or nameSuffix for resource isolation: Without prefixes, the same base deployed to multiple namespaces produces name conflicts. Use namePrefix: env- per overlay.

  2. Forgetting behavior: merge on ConfigMap/Secret generators: Without merge behavior, each overlay creates a separate ConfigMap instead of extending the base.

  3. Patching the wrong target: Patch targets must match group, version, kind, name, namespace, and labelSelector. Overly broad patches affect unintended resources.

  4. Using strategic merge when JSON patch is needed: Strategic merge patches can only modify existing fields. JSON 6902 patches add, remove, or replace arbitrary paths.

  5. Not validating output before apply: Always run kubectl kustomize overlay/ | kubectl apply --dry-run=client -f - to catch syntax errors before making cluster changes.

Practice Questions

  1. How does Kustomize differ from Helm? Answer: Kustomize is template-free — it patches raw YAML using overlays. Helm uses Go templates. Kustomize is built into kubectl; Helm is a separate CLI with release management.

  2. What is an overlay in Kustomize? Answer: An overlay is a directory with a kustomization.yaml that references a base and adds patches, generators, or configuration changes for a specific environment.

  3. What is the difference between strategic merge and JSON patches? Answer: Strategic merge patches modify Kubernetes objects by merging YAML. JSON patches (JSON 6902) perform specific operations (add, remove, replace) on arbitrary paths.

  4. How do generators work in Kustomize? Answer: Generators create ConfigMaps and Secrets from literals, files, or .env entries. They automatically append content hashes to names, triggering pod rotation on change.

Challenge

Create a complete Kustomize setup for a microservice: write base manifests (Deployment, Service, ConfigMap, Ingress), create overlays for dev (1 replica, debug logging, local API URL), staging (3 replicas, warn logging, staging API), and production (5+ replicas with HPA, resource limits, TLS Ingress, production secrets). Use strategic merge patches for resource changes and JSON patches for adding environment variables. Deploy each overlay to its respective namespace.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro