Kustomize Kubernetes Configuration Management

Kustomize is a Kubernetes-native configuration management tool that lets you customize YAML manifests without templating — using bases and overlays to manage environment-specific differences. Built into kubectl since 1.14, Kustomize is essential for managing Kubernetes configuration across development, staging, and production environments.

Prerequisites

  • kubectl 1.14+ (Kustomize is built in)
  • Kubernetes cluster access
  • Basic familiarity with Kubernetes YAML manifests

Install Kustomize

Kustomize is bundled with kubectl, but installing the standalone binary gives you the latest version:

# Install standalone Kustomize CLI (recommended for latest features)
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
sudo mv kustomize /usr/local/bin/

# Verify installation
kustomize version

# Alternatively, use kubectl's built-in kustomize
kubectl kustomize --help

Understanding Bases and Overlays

Kustomize uses a layering approach:

k8s/
├── base/                   # Common configuration shared across environments
│   ├── kustomization.yaml
│   ├── deployment.yaml
│   └── service.yaml
└── overlays/               # Environment-specific customizations
    ├── development/
    │   ├── kustomization.yaml
    │   └── replica-patch.yaml
    ├── staging/
    │   ├── kustomization.yaml
    │   └── resource-patch.yaml
    └── production/
        ├── kustomization.yaml
        └── hpa.yaml
  • Base: vanilla Kubernetes manifests with sensible defaults
  • Overlays: target a base and apply patches, new resources, or transformations

Creating a Base Configuration

# k8s/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  labels:
    app: web-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
        - name: web-app
          image: my-org/web-app:latest
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi
          env:
            - name: LOG_LEVEL
              value: info
# k8s/base/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: web-app
spec:
  selector:
    app: web-app
  ports:
    - port: 80
      targetPort: 8080
# k8s/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml

commonLabels:
  managed-by: kustomize
  app.kubernetes.io/name: web-app
# Preview the base output
kustomize build k8s/base/

# Apply the base directly
kubectl apply -k k8s/base/

Environment Overlays

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

# Reference the base
bases:
  - ../../base

# Add namespace
namespace: production

# Override the image tag for this environment
images:
  - name: my-org/web-app
    newTag: v1.2.3

# Additional resources specific to production
resources:
  - hpa.yaml
  - ingress.yaml

# Apply patches
patches:
  - path: replica-patch.yaml
    target:
      kind: Deployment
      name: web-app
  - path: resource-limits-patch.yaml

# Add production-specific labels
commonLabels:
  environment: production
# k8s/overlays/production/replica-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 3
# k8s/overlays/development/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

bases:
  - ../../base

namespace: development

images:
  - name: my-org/web-app
    newTag: latest

# Add name prefix to avoid conflicts
namePrefix: dev-

commonLabels:
  environment: development
# Preview production manifests
kustomize build k8s/overlays/production/

# Apply production overlay
kubectl apply -k k8s/overlays/production/

# Diff current cluster state vs kustomize output
kubectl diff -k k8s/overlays/production/

Patches and Transformers

Kustomize supports multiple patch formats:

Strategic Merge Patch (merge-based, uses Kubernetes merge strategy):

# resource-limits-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  template:
    spec:
      containers:
        - name: web-app
          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: 2000m
              memory: 2Gi
          env:
            - name: LOG_LEVEL
              value: warn

JSON 6902 Patch (precise, operation-based):

# json-patch.yaml
- op: replace
  path: /spec/replicas
  value: 5
- op: add
  path: /spec/template/spec/containers/0/env/-
  value:
    name: FEATURE_FLAG
    value: "enabled"
- op: remove
  path: /spec/template/spec/containers/0/livenessProbe
# Reference JSON patch in kustomization.yaml
patches:
  - path: json-patch.yaml
    target:
      kind: Deployment
      name: web-app
    options:
      allowNameChange: false

Inline patches (no separate file needed):

patches:
  - target:
      kind: Deployment
      labelSelector: "app=web-app"
    patch: |-
      - op: add
        path: /spec/template/spec/containers/0/env/-
        value:
          name: ENVIRONMENT
          value: production

Generators for ConfigMaps and Secrets

Kustomize can generate ConfigMaps and Secrets from files or literals, and automatically adds a hash suffix to trigger rolling updates:

# kustomization.yaml
configMapGenerator:
  - name: app-config
    literals:
      - LOG_LEVEL=info
      - MAX_CONNECTIONS=100
    files:
      - config.properties

  - name: nginx-config
    files:
      - nginx.conf

secretGenerator:
  - name: app-secrets
    literals:
      - DB_PASSWORD=supersecret
      - API_KEY=myapikey
    options:
      disableNameSuffixHash: false   # Keep hash for auto-rolling updates

  - name: tls-secret
    files:
      - tls.crt
      - tls.key
    type: kubernetes.io/tls
# Verify the generated ConfigMap
kustomize build . | grep -A 20 "kind: ConfigMap"

# The hash is added automatically: app-config-7fghj9k2m
# Deployments referencing this ConfigMap auto-roll when config changes

CI/CD Integration

GitHub Actions:

# .github/workflows/deploy.yml
name: Deploy to Kubernetes

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up kubectl
        uses: azure/setup-kubectl@v3

      - name: Configure kubeconfig
        run: |
          echo "${{ secrets.KUBECONFIG }}" | base64 -d > /tmp/kubeconfig
          export KUBECONFIG=/tmp/kubeconfig

      - name: Update image tag
        run: |
          cd k8s/overlays/production
          kustomize edit set image my-org/web-app=my-org/web-app:${{ github.sha }}

      - name: Deploy
        run: |
          kubectl apply -k k8s/overlays/production/
          kubectl rollout status deployment/web-app -n production

GitLab CI:

# .gitlab-ci.yml
deploy-production:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kustomize edit set image my-org/web-app=my-org/web-app:$CI_COMMIT_SHA
      --kustomization=k8s/overlays/production/kustomization.yaml
    - kubectl apply -k k8s/overlays/production/
  only:
    - main

Troubleshooting

Build errors — resource not found:

# Validate kustomization structure
kustomize build k8s/overlays/production/ 2>&1

# Check all referenced files exist
ls k8s/base/*.yaml k8s/overlays/production/*.yaml

Patches not applying:

# Use kustomize build to see the rendered output
kustomize build k8s/overlays/production/ | grep -A 5 "replicas"

# Ensure the target name in the patch matches the resource name exactly
# Common error: strategic merge patch name doesn't match resource name

Namespace not being set:

# Verify namespace field is set in overlay kustomization.yaml
grep namespace k8s/overlays/production/kustomization.yaml

# Note: namespace field does NOT apply to Namespace resources themselves

ConfigMap hash changing unexpectedly:

# If you want a stable name (no hash suffix):
configMapGenerator:
  - name: my-config
    literals:
      - KEY=value
    options:
      disableNameSuffixHash: true

Conclusion

Kustomize provides a clean, template-free approach to Kubernetes configuration management by layering overlays on top of shared bases. Its built-in generators, patch mechanisms, and transformers handle the most common environment customization patterns without introducing complex templating syntax. Integrate Kustomize into your CI/CD pipelines to automate image tag updates and maintain a single source of truth for cluster configuration in Git.