Skip to main content

Overview

Kustomize is a template-free way to customize Kubernetes configurations. It allows you to maintain a base configuration and apply environment-specific overlays without duplicating YAML files.
Kustomize is built into kubectl (version 1.14+), making it a native Kubernetes configuration management tool with no external dependencies.

Why Kustomize?

Template-Free

  • No template language to learn
  • Pure Kubernetes YAML
  • Easy to validate
  • Better IDE support

DRY Principle

  • Define once, reuse everywhere
  • Environment-specific overlays
  • No duplication
  • Easy to maintain

Native Integration

  • Built into kubectl
  • No additional tools needed
  • GitOps friendly
  • ArgoCD/Flux support

Composability

  • Layer configurations
  • Patch specific fields
  • Merge resources
  • Transform objects

Directory Structure

k8s/
├── base/                      # Base configurations
│   ├── kustomization.yaml
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── configmap.yaml
│   └── serviceaccount.yaml

├── overlays/                  # Environment overlays
│   ├── development/
│   │   ├── kustomization.yaml
│   │   ├── deployment-patch.yaml
│   │   └── configmap.yaml
│   │
│   ├── staging/
│   │   ├── kustomization.yaml
│   │   ├── deployment-patch.yaml
│   │   ├── hpa.yaml
│   │   └── configmap.yaml
│   │
│   └── production/
│       ├── kustomization.yaml
│       ├── deployment-patch.yaml
│       ├── hpa.yaml
│       ├── pdb.yaml
│       └── configmap.yaml

└── components/                # Optional components
    ├── redis/
    │   ├── kustomization.yaml
    │   └── redis.yaml
    ├── keycloak/
    │   ├── kustomization.yaml
    │   └── keycloak.yaml
    └── openfga/
        ├── kustomization.yaml
        └── openfga.yaml

Base Configuration

base/kustomization.yaml

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

namespace: mcp-server-langgraph

## Common labels applied to all resources
commonLabels:
  app: mcp-server-langgraph
  project: mcp-server

## Common annotations
commonAnnotations:
  managed-by: kustomize

## Resources
resources:
  - deployment.yaml
  - service.yaml
  - serviceaccount.yaml
  - configmap.yaml

## ConfigMap generator
configMapGenerator:
  - name: langgraph-config
    literals:
      - LOG_LEVEL=info
      - AUTH_PROVIDER=inmemory
      - SESSION_PROVIDER=memory

## Secret generator
secretGenerator:
  - name: langgraph-secrets
    literals:
      - JWT_SECRET=changeme
    type: Opaque

## Images to use
images:
  - name: mcp-server-langgraph
    newName: mcp-server-langgraph
    newTag: latest

base/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mcp-server-langgraph
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mcp-server-langgraph
  template:
    metadata:
      labels:
        app: mcp-server-langgraph
    spec:
      serviceAccountName: mcp-server-langgraph
      containers:
      - name: agent
        image: mcp-server-langgraph:latest
        ports:
        - containerPort: 8000
          name: http
        - containerPort: 9090
          name: metrics
        envFrom:
        - configMapRef:
            name: langgraph-config
        - secretRef:
            name: langgraph-secrets
        livenessProbe:
          httpGet:
            path: /health/live
            port: 8000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health/ready
            port: 8000
          initialDelaySeconds: 10
          periodSeconds: 5
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"

base/service.yaml

apiVersion: v1
kind: Service
metadata:
  name: mcp-server-langgraph
spec:
  type: ClusterIP
  selector:
    app: mcp-server-langgraph
  ports:
  - name: http
    port: 8000
    targetPort: 8000
  - name: metrics
    port: 9090
    targetPort: 9090

base/serviceaccount.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: mcp-server-langgraph

Development Overlay

overlays/development/kustomization.yaml

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

## Namespace
namespace: langgraph-dev

## Base
bases:
  - ../../base

## Name prefix
namePrefix: dev-

## Labels
commonLabels:
  environment: development

## ConfigMap override
configMapGenerator:
  - name: langgraph-config
    behavior: merge
    literals:
      - ENV=development
      - DEBUG=true
      - LOG_LEVEL=debug
      - AUTH_PROVIDER=inmemory
      - SESSION_PROVIDER=memory
      - ENABLE_TRACING=false
      - ENABLE_METRICS=false

## Patches
patchesStrategicMerge:
  - deployment-patch.yaml

## Images
images:
  - name: mcp-server-langgraph
    newName: mcp-server-langgraph
    newTag: dev

overlays/development/deployment-patch.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mcp-server-langgraph
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: agent
        resources:
          requests:
            memory: "256Mi"
            cpu: "100m"
          limits:
            memory: "512Mi"
            cpu: "250m"

Staging Overlay

overlays/staging/kustomization.yaml

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

namespace: langgraph-staging

bases:
  - ../../base

namePrefix: staging-

commonLabels:
  environment: staging

configMapGenerator:
  - name: langgraph-config
    behavior: merge
    literals:
      - ENV=staging
      - DEBUG=false
      - LOG_LEVEL=info
      - AUTH_PROVIDER=keycloak
      - SESSION_PROVIDER=redis
      - KEYCLOAK_URL=http://keycloak:8080
      - REDIS_URL=redis://redis-master:6379
      - OPENFGA_ENABLED=true
      - OPENFGA_URL=http://openfga:8080
      - ENABLE_TRACING=true
      - ENABLE_METRICS=true

resources:
  - hpa.yaml

patchesStrategicMerge:
  - deployment-patch.yaml

images:
  - name: mcp-server-langgraph
    newName: gcr.io/project-id/mcp-server-langgraph
    newTag: staging

overlays/staging/deployment-patch.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mcp-server-langgraph
spec:
  replicas: 2
  template:
    spec:
      containers:
      - name: agent
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"

overlays/staging/hpa.yaml

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: mcp-server-langgraph
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: mcp-server-langgraph
  minReplicas: 2
  maxReplicas: 5
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

Production Overlay

overlays/production/kustomization.yaml

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

namespace: mcp-server-langgraph

bases:
  - ../../base

## No prefix for production

commonLabels:
  environment: production

commonAnnotations:
  prometheus.io/scrape: "true"
  prometheus.io/port: "9090"

configMapGenerator:
  - name: langgraph-config
    behavior: merge
    literals:
      - ENV=production
      - DEBUG=false
      - LOG_LEVEL=warning
      - AUTH_PROVIDER=keycloak
      - SESSION_PROVIDER=redis
      - KEYCLOAK_URL=http://keycloak:8080
      - REDIS_URL=redis://redis-master:6379
      - OPENFGA_ENABLED=true
      - OPENFGA_URL=http://openfga:8080
      - ENABLE_TRACING=true
      - ENABLE_METRICS=true
      - LANGSMITH_ENABLED=true

resources:
  - hpa.yaml
  - pdb.yaml
  - network-policy.yaml

patchesStrategicMerge:
  - deployment-patch.yaml

images:
  - name: mcp-server-langgraph
    newName: gcr.io/project-id/mcp-server-langgraph
    newTag: v1.0.0

replicas:
  - name: mcp-server-langgraph
    count: 3

overlays/production/deployment-patch.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mcp-server-langgraph
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9090"
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - mcp-server-langgraph
              topologyKey: kubernetes.io/hostname
      containers:
      - name: agent
        resources:
          requests:
            memory: "1Gi"
            cpu: "500m"
          limits:
            memory: "2Gi"
            cpu: "1000m"

overlays/production/pdb.yaml

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: mcp-server-langgraph
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: mcp-server-langgraph

overlays/production/network-policy.yaml

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mcp-server-langgraph
spec:
  podSelector:
    matchLabels:
      app: mcp-server-langgraph
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: ingress-nginx
    ports:
    - protocol: TCP
      port: 8000
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: keycloak
    ports:
    - protocol: TCP
      port: 8080
  - to:
    - podSelector:
        matchLabels:
          app: redis
    ports:
    - protocol: TCP
      port: 6379
  - to:
    - podSelector:
        matchLabels:
          app: openfga
    ports:
    - protocol: TCP
      port: 8080
  - to:
    - namespaceSelector: {}
    ports:
    - protocol: TCP
      port: 53
    - protocol: UDP
      port: 53

Advanced Techniques

JSON Patches

## overlays/production/kustomization.yaml
patchesJson6902:
  - target:
      group: apps
      version: v1
      kind: Deployment
      name: mcp-server-langgraph
    patch: |-
      - op: add
        path: /spec/template/spec/containers/0/env/-
        value:
          name: CUSTOM_ENV
          value: "production-value"

Strategic Merge Patch

## Add sidecar container
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mcp-server-langgraph
spec:
  template:
    spec:
      containers:
      - name: cloud-sql-proxy
        image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:latest
        args:
          - "--structured-logs"
          - "--port=5432"
          - "project:region:instance"

Transformers

## overlays/production/kustomization.yaml
transformers:
  - |-
    apiVersion: builtin
    kind: PrefixSuffixTransformer
    metadata:
      name: prefix-suffix
    prefix: prod-
    suffix: -v1
    fieldSpecs:
      - path: metadata/name

Generators

## overlays/production/kustomization.yaml
generators:
  - secret-generator.yaml
secret-generator.yaml:
apiVersion: builtin
kind: SecretGenerator
metadata:
  name: db-credentials
literals:
  - username=admin
  - password=changeme
type: Opaque

Multi-Environment Workflow

Build and Preview

## Development
kubectl kustomize overlays/development

## Staging
kubectl kustomize overlays/staging

## Production
kubectl kustomize overlays/production

Deploy

## Development
kubectl apply -k overlays/development

## Staging
kubectl apply -k overlays/staging

## Production
kubectl apply -k overlays/production

Delete

## Delete environment
kubectl delete -k overlays/staging

CI/CD Integration

GitHub Actions

name: Deploy with Kustomize

on:
  push:
    branches: [main]

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

      - name: Setup kubectl
        uses: azure/setup-kubectl@v3

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

      - name: Deploy to production
        run: |
          kubectl apply -k k8s/overlays/production

      - name: Verify deployment
        run: |
          kubectl rollout status deployment/mcp-server-langgraph -n mcp-server-langgraph

GitLab CI

deploy:production:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl apply -k k8s/overlays/production
    - kubectl rollout status deployment/mcp-server-langgraph -n mcp-server-langgraph
  only:
    - main

GitOps with ArgoCD

Create Application

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: mcp-server-langgraph-production
  namespace: argocd
spec:
  project: default

  source:
    repoURL: https://github.com/vishnu2kmohan/mcp-server-langgraph
    targetRevision: main
    path: k8s/overlays/production

  destination:
    server: https://kubernetes.default.svc
    namespace: mcp-server-langgraph

  syncPolicy:
    automated:
      prune: true
      selfHeal: true
      allowEmpty: false
    syncOptions:
      - CreateNamespace=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

Deploy with ArgoCD

## Install ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

## Create application
kubectl apply -f argocd-application.yaml

## Sync application
argocd app sync mcp-server-langgraph-production

Best Practices

Always use generators instead of static files:
# Good
configMapGenerator:
  - name: app-config
    literals:
      - KEY=value

# Bad - don't commit static ConfigMaps
resources:
  - configmap.yaml  # Contains hardcoded values
Base should contain only common, environment-agnostic configuration:
  • Deployment structure
  • Service definition
  • Basic probes
  • No environment-specific values
Tag images with semantic versions:
images:
  - name: mcp-server-langgraph
    newTag: v1.2.3  # Not 'latest'
Always validate Kustomize output:
# Preview changes
kubectl kustomize overlays/production

# Validate
kubectl kustomize overlays/production | kubectl apply --dry-run=client -f -

# Then apply
kubectl apply -k overlays/production
Enable automatic rollouts on config changes:
generatorOptions:
  disableNameSuffixHash: false  # Default
This adds hash suffix to ConfigMap names, triggering pod restarts.

Troubleshooting

Error: Error: accumulating resources: accumulation err='accumulating resources from '../../base': ...Solution:
# Validate base first
kubectl kustomize k8s/base

# Check file references
ls -la k8s/base/

# Validate overlay
kubectl kustomize k8s/overlays/production
Problem: Strategic merge patch not workingSolution: Use JSON6902 patch instead:
patchesJson6902:
  - target:
      group: apps
      version: v1
      kind: Deployment
      name: mcp-server-langgraph
    patch: |-
      - op: replace
        path: /spec/replicas
        value: 5
Problem: Pods not restarting on ConfigMap changeSolution: Ensure name suffix hash is enabled:
generatorOptions:
  disableNameSuffixHash: false
Or use Reloader:
helm install reloader stakater/reloader

Testing Kustomize

## Validate YAML syntax
kubectl kustomize overlays/production | kubectl apply --dry-run=client -f -

## Check resource counts
kubectl kustomize overlays/production | grep "^kind:" | sort | uniq -c

## Diff between environments
diff <(kubectl kustomize overlays/staging) <(kubectl kustomize overlays/production)

## Test with kubeval
kubectl kustomize overlays/production | kubeval --strict

## Test with kube-score
kubectl kustomize overlays/production | kube-score score -

Next Steps


Kustomize Ready: Template-free Kubernetes configuration management!