Kubernetes Kustomize: Manage Configs Without Helm
Learn Kustomize for Kubernetes config management. Covers overlays, patches, generators, and multi-environment deployments without templating.
What you'll learn
- ✓Structure a Kustomize project with bases and overlays
- ✓Use patches to modify resources per environment
- ✓Generate ConfigMaps and Secrets from files
- ✓Deploy multi-environment apps without Helm or templates
Prerequisites
- •Basic Kubernetes knowledge — see /blog/kubernetes-pods-deployments-services
- •Familiarity with YAML and kubectl
Kustomize is a configuration management tool built directly into kubectl. It lets you customize Kubernetes manifests without templates, variable substitution, or a separate tool installation. Instead of parameterizing YAML files with placeholders, you declare modifications as overlays on top of a base configuration.
The result is plain Kubernetes YAML that you can read, validate, and version control. No curly braces, no Go templates, no values files.
Why Kustomize Over Helm
Helm is a package manager with templating. Kustomize is a configuration customizer without templating. They solve overlapping but different problems:
- Helm excels when you distribute reusable charts that others consume. It supports complex logic, conditionals, and loops.
- Kustomize excels when you manage your own deployments across environments. It keeps manifests readable and avoids template complexity.
You can use both together — Kustomize can post-process Helm output. But for many teams managing their own services, Kustomize alone is sufficient and simpler.
Project Structure
A typical Kustomize project has a base and one overlay per environment:
app/
├── base/
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ ├── service.yaml
│ └── configmap.yaml
├── overlays/
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ └── replica-patch.yaml
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ └── replica-patch.yaml
│ └── production/
│ ├── kustomization.yaml
│ ├── replica-patch.yaml
│ └── hpa.yaml
The base contains the core manifests shared across all environments. Overlays add or modify resources for specific environments.
Building the Base
Start with standard Kubernetes manifests:
# 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: myregistry/web-app:latest
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
envFrom:
- configMapRef:
name: web-app-config
# base/service.yaml
apiVersion: v1
kind: Service
metadata:
name: web-app
spec:
selector:
app: web-app
ports:
- port: 80
targetPort: 8080
type: ClusterIP
# base/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: web-app-config
data:
LOG_LEVEL: "info"
DB_HOST: "localhost"
DB_PORT: "5432"
The base kustomization.yaml lists all resources:
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- configmap.yaml
commonLabels:
managed-by: kustomize
Preview the output:
# Run this in your terminal:
# kubectl kustomize base/
Creating Overlays
Dev Overlay
# overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: dev-
namespace: dev
patches:
- path: replica-patch.yaml
configMapGenerator:
- name: web-app-config
behavior: merge
literals:
- LOG_LEVEL=debug
- DB_HOST=dev-db.internal
# overlays/dev/replica-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 1
template:
spec:
containers:
- name: web-app
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
Production Overlay
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
- hpa.yaml
namePrefix: prod-
namespace: production
patches:
- path: replica-patch.yaml
images:
- name: myregistry/web-app
newTag: v2.1.0
configMapGenerator:
- name: web-app-config
behavior: merge
literals:
- LOG_LEVEL=warn
- DB_HOST=prod-db.internal
- DB_PORT=5432
# overlays/production/replica-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
template:
spec:
containers:
- name: web-app
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
# overlays/production/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: prod-web-app
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Deploy to each environment:
# Preview what will be applied:
# kubectl kustomize overlays/dev/
# kubectl kustomize overlays/production/
# Apply:
# kubectl apply -k overlays/dev/
# kubectl apply -k overlays/production/
Patch Strategies
Kustomize supports two patch formats.
Strategic Merge Patches
These merge with the existing resource. You only specify the fields you want to change:
# Add an annotation to the deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
annotations:
monitoring.example.com/enabled: "true"
JSON Patches
For precise modifications like removing fields or changing array elements:
# overlays/production/kustomization.yaml
patches:
- target:
kind: Deployment
name: web-app
patch: |
- op: add
path: /spec/template/spec/containers/0/livenessProbe
value:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
- op: add
path: /spec/template/spec/containers/0/readinessProbe
value:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
Inline Patches
You can write patches inline in kustomization.yaml:
patches:
- target:
kind: Service
name: web-app
patch: |
apiVersion: v1
kind: Service
metadata:
name: web-app
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb
spec:
type: LoadBalancer
ConfigMap and Secret Generators
Generators create ConfigMaps and Secrets with content-based hash suffixes. When the content changes, the name changes, triggering a rolling update:
# From literal values
configMapGenerator:
- name: web-app-config
literals:
- LOG_LEVEL=info
- APP_MODE=production
# From files
configMapGenerator:
- name: nginx-config
files:
- nginx.conf
- configs/upstream.conf
# From env file
configMapGenerator:
- name: env-config
envs:
- .env.production
Secret generators work the same way:
secretGenerator:
- name: db-credentials
literals:
- DB_USER=admin
- DB_PASSWORD=supersecret
type: Opaque
- name: tls-cert
files:
- tls.crt
- tls.key
type: kubernetes.io/tls
The hash suffix (e.g., web-app-config-8m2dk4) means old ConfigMaps are not deleted immediately. Clean up stale ones with kubectl delete configmap -l managed-by=kustomize or let garbage collection handle them.
Transformers
commonLabels and commonAnnotations
commonLabels:
team: platform
environment: production
commonAnnotations:
contact: platform-team@example.com
These are applied to all resources and their selectors.
namePrefix and nameSuffix
namePrefix: prod-
nameSuffix: -v2
The deployment named web-app becomes prod-web-app-v2. Kustomize automatically updates all references (service selectors, HPA targets, etc.).
Image Overrides
images:
- name: myregistry/web-app
newName: production-registry.example.com/web-app
newTag: v2.1.0
- name: nginx
newTag: "1.25"
- name: redis
newName: custom-redis
digest: sha256:abc123def456
This is cleaner than patching the deployment just to change an image tag.
Components for Shared Features
Components let you define reusable pieces of configuration that can be included in multiple overlays:
# components/monitoring/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
patches:
- target:
kind: Deployment
patch: |
apiVersion: apps/v1
kind: Deployment
metadata:
name: not-used
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
spec:
template:
spec:
containers:
- name: metrics-sidecar
image: prom/pushgateway:v1.6.0
ports:
- containerPort: 9090
Include the component in overlays:
# overlays/production/kustomization.yaml
components:
- ../../components/monitoring
Multi-Service Applications
For microservices, organize each service as its own base:
platform/
├── services/
│ ├── api/
│ │ ├── base/
│ │ │ ├── kustomization.yaml
│ │ │ ├── deployment.yaml
│ │ │ └── service.yaml
│ │ └── overlays/
│ │ ├── dev/
│ │ └── production/
│ ├── worker/
│ │ ├── base/
│ │ └── overlays/
│ └── frontend/
│ ├── base/
│ └── overlays/
└── environments/
├── dev/
│ └── kustomization.yaml
└── production/
└── kustomization.yaml
The environment kustomization combines all services:
# environments/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../services/api/overlays/production
- ../../services/worker/overlays/production
- ../../services/frontend/overlays/production
namespace: production
CI/CD Integration
Build manifests in CI and apply them:
# .github/workflows/deploy.yml (simplified)
# steps:
# - name: Build manifests
# run: kubectl kustomize overlays/$ENVIRONMENT > manifests.yaml
#
# - name: Validate
# run: kubectl apply --dry-run=server -f manifests.yaml
#
# - name: Deploy
# run: kubectl apply -f manifests.yaml
You can also use Kustomize with GitOps tools like ArgoCD and Flux, which natively understand kustomization.yaml files.
Wrapping Up
Kustomize provides a template-free approach to Kubernetes configuration management that keeps your manifests readable and maintainable:
- Bases hold shared configuration; overlays customize per environment.
- Patches modify resources with strategic merge or JSON patch operations.
- Generators create ConfigMaps and Secrets with automatic hash suffixes for safe rollouts.
- Transformers apply labels, prefixes, and image overrides across all resources.
- Components package reusable features for inclusion in multiple overlays.
Since Kustomize is built into kubectl (kubectl apply -k), there is nothing extra to install. Start by organizing your existing manifests into a base directory, create overlays for your environments, and gradually adopt generators and patches as your needs grow.
Related articles
- Kubernetes Kubernetes Cost Optimization: Right-Size Your Cluster
Reduce Kubernetes costs with right-sizing, autoscaling, spot instances, resource quotas, and monitoring. Practical strategies with real savings.
- Kubernetes Kubernetes ConfigMaps and Secrets Tutorial
A practical walkthrough of ConfigMaps and Secrets in Kubernetes, including how to inject them as environment variables, mount as files, and rotate safely.
- Kubernetes Kubernetes Init Containers: A Practical Tutorial
Learn how Kubernetes init containers work, when to use them for setup tasks, and how to build robust pod initialization pipelines with real YAML examples.
- Kubernetes Kubernetes Readiness vs Liveness Probes: A Practical Guide
Understand the difference between readiness and liveness probes in Kubernetes, when to use each, and how to configure them safely in production workloads.