Kustomize for Kubernetes Configuration Management
Kustomize provides template-free customization of Kubernetes manifests, enabling environment-specific variations without duplicating YAML. Built into kubectl, it manages configuration variations through strategic patches and overlays.
What is Kustomize
Kustomize transforms Kubernetes manifests without templates, using declarative overlays to customize base configurations.
Template-Free Approach
Traditional template approach with Helm:
# values.yaml
replicaCount: {{ .Values.replicaCount }}
image:
repository: {{ .Values.image.repository }}
tag: {{ .Values.image.tag }}
Kustomize approach:
# base/deployment.yaml - Plain Kubernetes YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service
spec:
replicas: 3
template:
spec:
containers:
- name: api
image: registry.example.com/api-service:v1.0.0
No templating syntax - pure Kubernetes manifests that work directly with kubectl.
Built Into kubectl
Kustomize integrates natively with kubectl:
# Apply kustomization directly
kubectl apply -k overlays/production/
# Preview generated manifests
kubectl kustomize overlays/production/
# Diff against cluster
kubectl diff -k overlays/production/
No separate tool installation required for basic usage.
graph TD
A[Base Manifests] --> B[Kustomization]
C[Overlay Patches] --> B
D[Environment Vars] --> B
B --> E[kubectl apply -k]
E --> F[Kubernetes Cluster]
style B fill:#e1f5ff
style F fill:#d4f1d4
Kustomize Structure
Kustomize organizes manifests using base and overlay directories.
Base Configuration
Base contains common configuration:
k8s/
└── base/
├── kustomization.yaml
├── deployment.yaml
├── service.yaml
└── configmap.yaml
Base deployment:
# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service
labels:
app: api-service
spec:
replicas: 3
selector:
matchLabels:
app: api-service
template:
metadata:
labels:
app: api-service
spec:
containers:
- name: api
image: registry.example.com/api-service:v1.0.0
ports:
- containerPort: 8080
env:
- name: LOG_LEVEL
value: info
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
Base service:
# base/service.yaml
apiVersion: v1
kind: Service
metadata:
name: api-service
spec:
selector:
app: api-service
ports:
- port: 80
targetPort: 8080
type: ClusterIP
Base kustomization:
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- configmap.yaml
commonLabels:
app: api-service
managed-by: kustomize
Overlays
Overlays customize base for specific environments:
k8s/
├── base/
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ └── service.yaml
└── overlays/
├── development/
│ ├── kustomization.yaml
│ └── replica-patch.yaml
├── staging/
│ ├── kustomization.yaml
│ └── replica-patch.yaml
└── production/
├── kustomization.yaml
├── replica-patch.yaml
└── hpa.yaml
Development overlay:
# overlays/development/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../../base
namePrefix: dev-
commonLabels:
environment: development
replicas:
- name: api-service
count: 1
images:
- name: registry.example.com/api-service
newTag: dev-latest
configMapGenerator:
- name: api-config
behavior: merge
literals:
- LOG_LEVEL=debug
Production overlay:
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../../base
namePrefix: prod-
commonLabels:
environment: production
replicas:
- name: api-service
count: 5
images:
- name: registry.example.com/api-service
newTag: v2.1.0
resources:
- hpa.yaml
patches:
- path: replica-patch.yaml
target:
kind: Deployment
name: api-service
configMapGenerator:
- name: api-config
behavior: merge
literals:
- LOG_LEVEL=warn
Production HPA:
# overlays/production/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-service
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-service
minReplicas: 5
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Patching Strategies
Kustomize supports multiple patching approaches for customization.
Strategic Merge Patch
Merge patches into existing manifests:
# overlays/production/replica-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service
spec:
replicas: 5
template:
spec:
containers:
- name: api
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 1000m
memory: 1Gi
Strategic merge intelligently combines with base deployment.
JSON Patch
Precise modifications using JSON patch syntax:
# overlays/staging/kustomization.yaml
patchesJson6902:
- target:
version: v1
kind: Deployment
name: api-service
patch: |-
- op: replace
path: /spec/replicas
value: 3
- op: add
path: /spec/template/spec/containers/0/env/-
value:
name: FEATURE_FLAG_NEW_API
value: "true"
- op: remove
path: /spec/template/spec/containers/0/env/0
JSON patches provide surgical precision for complex modifications.
Patch Transformers
Apply patches based on selectors:
# kustomization.yaml
patches:
- target:
kind: Deployment
labelSelector: "tier=backend"
patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: not-important
spec:
template:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
Transformers apply changes to multiple resources matching criteria.
Kustomization Features
Kustomize provides powerful features for manifest management.
Image Management
Update container images without editing manifests:
# kustomization.yaml
images:
- name: registry.example.com/api-service
newName: registry.example.com/api-service-v2
newTag: 2.1.0
# Or using digest for immutability
- name: registry.example.com/api-service
digest: sha256:abc123...
Image transformers enable CI/CD integration:
# Update image tag from CI pipeline
kustomize edit set image registry.example.com/api-service:$GIT_SHA
# Generated kustomization.yaml includes:
# images:
# - name: registry.example.com/api-service
# newTag: abc123def
ConfigMap and Secret Generators
Generate ConfigMaps and Secrets from files or literals:
# kustomization.yaml
configMapGenerator:
- name: app-config
files:
- configs/application.properties
- configs/logging.conf
literals:
- DATABASE_HOST=postgresql.production.svc
- DATABASE_PORT=5432
secretGenerator:
- name: app-secrets
files:
- secrets/api-key.txt
- secrets/database-password.txt
literals:
- ADMIN_PASSWORD=ChangeMe123
options:
disableNameSuffixHash: false
Generators automatically append hashes to names, triggering pod restarts when configurations change:
# Generated ConfigMap name
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-8m2d4b7gkf
data:
DATABASE_HOST: postgresql.production.svc
DATABASE_PORT: "5432"
Name Prefix and Suffix
Add prefixes or suffixes to resource names:
# kustomization.yaml
namePrefix: prod-
nameSuffix: -v2
resources:
- deployment.yaml # Becomes prod-api-service-v2
- service.yaml # Becomes prod-api-service-v2
Naming conventions support multi-tenancy and versioning.
Label and Annotation Transformers
Add labels or annotations to all resources:
# kustomization.yaml
commonLabels:
environment: production
team: platform
cost-center: engineering
commonAnnotations:
managed-by: kustomize
deployment-date: "2024-08-31"
contact: platform-team@example.com
Labels and annotations propagate to all resources including selectors.
Composition Patterns
Combine multiple kustomizations for complex scenarios.
Multi-Base Composition
Reference multiple bases:
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../../components/api-service
- ../../components/database
- ../../components/monitoring
namePrefix: prod-
commonLabels:
environment: production
Multi-base composition assembles complete applications from components.
Components
Reusable configuration fragments:
k8s/
├── base/
│ └── kustomization.yaml
├── components/
│ ├── monitoring/
│ │ ├── kustomization.yaml
│ │ ├── servicemonitor.yaml
│ │ └── prometheusrule.yaml
│ ├── tls/
│ │ ├── kustomization.yaml
│ │ └── certificate.yaml
│ └── vault-integration/
│ ├── kustomization.yaml
│ └── external-secret.yaml
└── overlays/
└── production/
└── kustomization.yaml
Component kustomization:
# components/monitoring/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
resources:
- servicemonitor.yaml
- prometheusrule.yaml
Include components selectively:
# overlays/production/kustomization.yaml
bases:
- ../../base
components:
- ../../components/monitoring
- ../../components/tls
- ../../components/vault-integration
graph TD
A[Base Configuration] --> E[Production Overlay]
B[Monitoring Component] --> E
C[TLS Component] --> E
D[Vault Component] --> E
E --> F[Final Manifests]
F --> G[kubectl apply]
style E fill:#fff4e6
style F fill:#e1f5ff
Integration with CI/CD
Automate Kustomize in deployment pipelines.
GitOps with ArgoCD
ArgoCD supports Kustomize natively:
# argocd-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api-service
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/organization/k8s-manifests
targetRevision: main
path: overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
ArgoCD automatically detects Kustomize and applies overlays.
CI Pipeline Integration
Update images from CI pipeline:
# .github/workflows/deploy.yaml
name: Build and Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout application code
uses: actions/checkout@v3
path: app
- name: Build and push image
working-directory: app
run: |
docker build -t registry.example.com/api-service:${{ github.sha }} .
docker push registry.example.com/api-service:${{ github.sha }}
- name: Checkout manifests repo
uses: actions/checkout@v3
with:
repository: organization/k8s-manifests
path: manifests
token: ${{ secrets.GITHUB_TOKEN }}
- name: Update image tag
working-directory: manifests/overlays/production
run: |
kustomize edit set image registry.example.com/api-service:${{ github.sha }}
- name: Commit and push
working-directory: manifests
run: |
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add overlays/production/kustomization.yaml
git commit -m "Update api-service to ${{ github.sha }}"
git push
Pipeline updates kustomization, triggering ArgoCD sync.
Advanced Techniques
Sophisticated Kustomize usage for complex requirements.
Remote Bases
Reference bases from external repositories:
# kustomization.yaml
bases:
- github.com/kubernetes-sigs/kustomize/examples/multibases/dev/?ref=v3.7.0
- https://github.com/organization/k8s-common//base?ref=v1.2.0
resources:
- deployment.yaml
Remote bases enable sharing common configurations across teams.
Kustomization Transformers
Custom transformers with plugins:
# kustomization.yaml
transformers:
- |-
apiVersion: builtin
kind: PrefixSuffixTransformer
metadata:
name: customTransformer
prefix: prod-
fieldSpecs:
- path: metadata/name
kind: ConfigMap
- |-
apiVersion: builtin
kind: LabelTransformer
metadata:
name: labelTransformer
labels:
version: v2.1.0
fieldSpecs:
- path: metadata/labels
create: true
Variable Substitution
Replace variables in manifests:
# kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
vars:
- name: SERVICE_NAME
objref:
kind: Service
name: api-service
apiVersion: v1
fieldref:
fieldpath: metadata.name
configurations:
- varreference.yaml
Variable reference configuration:
# varreference.yaml
varReference:
- path: spec/template/spec/containers/env/value
kind: Deployment
Deployment using variable:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service
spec:
template:
spec:
containers:
- name: api
env:
- name: SERVICE_NAME
value: $(SERVICE_NAME)
Kustomize vs Helm
Understanding when to use each tool:
| Aspect | Kustomize | Helm |
|---|---|---|
| Templates | No templates, pure YAML | Go templates |
| Learning Curve | Shallow | Steeper |
| Complexity | Simple overlays | Complex logic possible |
| kubectl Integration | Native | Separate tool |
| Package Management | No | Yes (charts as packages) |
| Version Management | Git-based | Chart versions |
| Dependencies | File-based | Chart dependencies |
Use Kustomize when:
- Prefer template-free approach
- Simple environment variations
- Want kubectl integration
- Git-based workflow sufficient
Use Helm when:
- Need package management
- Complex conditional logic required
- Want dependency management
- Publishing/sharing charts externally
Common Pitfalls
Name Conflicts: Multiple overlays using same names cause conflicts. Use unique prefixes per environment.
Patch Ordering: Patches apply in specific order. Test generated manifests to verify expected results.
Base Modifications: Changing bases affects all overlays. Test overlays after base changes.
ConfigMap Hash Suffix: Generated names with hashes break hard-coded references. Use variable substitution or disable hashing carefully.
Key Takeaways
- Kustomize provides template-free Kubernetes configuration management through declarative overlays on base manifests
- Structure code with base directory containing common configuration and overlay directories for environment-specific customizations
- Use strategic merge patches for simple changes, JSON patches for precise modifications, and transformers for selector-based patches
- Leverage image management for tag updates, generators for ConfigMaps/Secrets, and name/label transformers for consistency
- Compose complex applications from multiple bases and reusable components selected per environment
- Integrate with ArgoCD for GitOps or CI/CD pipelines for automated image tag updates triggering deployments
- Choose Kustomize for simple overlays with kubectl integration or Helm for complex templating and package management
- Avoid name conflicts with unique prefixes, verify patch ordering results, test overlays after base changes, and handle ConfigMap hash suffixes appropriately