Kustomize environments let you manage configuration for different deployment stages without duplicating your base Kubernetes manifests.

Imagine you’re deploying a web application. You have a Deployment and a Service manifest. That’s your base. Now, for production, you want more replicas, maybe a different ingress class, and a specific resource limit. For staging, you might want fewer replicas but still a different ingress. For development, you want it all to be as lightweight as possible.

Here’s how you’d set that up with Kustomize.

First, your base directory:

# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: app-container
        image: nginx:latest
        ports:
        - containerPort: 80
# base/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: ClusterIP

Now, let’s define our environments. Each environment will have its own kustomization.yaml that references the base and applies specific patches or changes.

Development Environment

This is often the closest to the base, maybe just enabling debug logging or a different image tag.

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

resources:
- ../../base

patchesStrategicMerge:
- deployment-dev-patch.yaml
# environments/dev/deployment-dev-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: app-container
        image: nginx:1.21.6 # Specific dev image

To see the output for dev: kustomize build environments/dev

Staging Environment

Here we might increase replicas and change the ingress.

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

resources:
- ../../base

patchesStrategicMerge:
- deployment-staging-patch.yaml
- service-staging-patch.yaml
# environments/staging/deployment-staging-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: app-container
        resources:
          limits:
            cpu: "500m"
            memory: "256Mi"
          requests:
            cpu: "200m"
            memory: "128Mi"
# environments/staging/service-staging-patch.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  type: LoadBalancer # Staging uses a LoadBalancer
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
    name: http

To see the output for staging: kustomize build environments/staging

Production Environment

This is where you’d typically have the most replicas and stricter resource limits.

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

resources:
- ../../base

patchesStrategicMerge:
- deployment-prod-patch.yaml
- service-prod-patch.yaml
# environments/prod/deployment-prod-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 10
  template:
    spec:
      containers:
      - name: app-container
        image: nginx:latest # Production image, could be a specific tag
        resources:
          limits:
            cpu: "1"
            memory: "512Mi"
          requests:
            cpu: "500m"
            memory: "256Mi"
# environments/prod/service-prod-patch.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  type: LoadBalancer # Production uses a LoadBalancer
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
    name: http

To see the output for prod: kustomize build environments/prod

The magic here is that kustomize build <environment_dir> will recursively find the kustomization.yaml, load the base resources, and then apply the patches defined in that environment’s kustomization.yaml. This means your core application manifests (base/deployment.yaml, base/service.yaml) are DRY, and the environment-specific overrides are isolated and clear.

A common pattern is to also use commonLabels or commonAnnotations within each kustomization.yaml to ensure consistent labeling across environments, which is crucial for selectors and monitoring.

If you want to add a new configuration file only for production, you’d add it to environments/prod/kustomization.yaml using the resources field, not in the base. For example, adding a ConfigMap for production:

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

resources:
- ../../base
- prod-configmap.yaml # New file for prod

patchesStrategicMerge:
- deployment-prod-patch.yaml
- service-prod-patch.yaml
# environments/prod/prod-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: prod-app-config
data:
  FEATURE_FLAG_X: "true"

This structure allows you to manage complex configurations for multiple environments efficiently, ensuring consistency while providing the necessary flexibility for each stage of your deployment lifecycle.

The next step is to integrate this into your CI/CD pipeline, using kubectl apply -k environments/prod or similar commands.

Want structured learning?

Take the full Kustomize course →