Kustomize Integration Guide

Learn how to use Kustomize with Project Planton for managing multi-environment deployments.


What is Kustomize?

Kustomize is a configuration management tool that lets you create variations of YAML files without duplication. Instead of maintaining separate manifests for dev/staging/prod, you maintain one base and environment-specific overlays that patch the base.

The Problem Without Kustomize:

manifests/
├── dev-database.yaml      # Lots of duplication
├── staging-database.yaml  # Same resource, different values
└── prod-database.yaml     # Hard to maintain consistency

The Solution With Kustomize:

manifests/database/
├── base/
│   └── database.yaml      # Shared configuration
└── overlays/
    ├── dev/
    ├── staging/
    └── prod/               # Environment-specific patches

The Clothing Analogy

Think of Kustomize like a clothing store:

  • Base = The standard shirt design (common to all)
  • Overlays = Size-specific modifications (small, medium, large)
  • Result = A shirt that fits each person, derived from the same base design

Project Planton integrates Kustomize seamlessly, building your manifest at deployment time.


Quick Start

1. Create Base Manifest

mkdir -p services/api/kustomize/base

services/api/kustomize/base/deployment.yaml:

apiVersion: kubernetes.project-planton.org/v1
kind: MicroserviceKubernetes
metadata:
  name: api
spec:
  container:
    image:
      repo: myapp/api
      tag: latest
    replicas: 1
    resources:
      limits:
        cpu: 500m
        memory: 512Mi

services/api/kustomize/base/kustomization.yaml:

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

resources:
  - deployment.yaml

2. Create Environment Overlay

services/api/kustomize/overlays/prod/kustomization.yaml:

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

resources:
  - ../../base

patches:
  - path: patch.yaml

services/api/kustomize/overlays/prod/patch.yaml:

apiVersion: kubernetes.project-planton.org/v1
kind: MicroserviceKubernetes
metadata:
  name: api
spec:
  container:
    image:
      tag: v1.0.0
    replicas: 3
    resources:
      limits:
        cpu: 2000m
        memory: 4Gi

3. Deploy with Project Planton

# Deploy to production
project-planton pulumi up \
  --kustomize-dir services/api/kustomize \
  --overlay prod

# Deploy to dev
project-planton pulumi up \
  --kustomize-dir services/api/kustomize \
  --overlay dev

What happens:

  1. Project Planton runs kustomize build services/api/kustomize/overlays/prod
  2. Merges base + prod overlay into final manifest
  3. Validates the result
  4. Deploys using Pulumi or OpenTofu

Directory Structure

Standard Layout

<service-name>/kustomize/
├── base/
│   ├── kustomization.yaml          # Base kustomization config
│   └── <resource>.yaml             # Base resource definition
└── overlays/
    ├── dev/
    │   ├── kustomization.yaml      # Dev environment config
    │   └── patch.yaml              # Dev-specific patches
    ├── staging/
    │   ├── kustomization.yaml
    │   └── patch.yaml
    └── prod/
        ├── kustomization.yaml
        └── patch.yaml

Example: Complete Service

backend/services/api/kustomize/
├── base/
│   ├── kustomization.yaml
│   ├── deployment.yaml
│   └── database.yaml
└── overlays/
    ├── dev/
    │   ├── kustomization.yaml
    │   ├── deployment-patch.yaml
    │   └── database-patch.yaml
    ├── staging/
    │   ├── kustomization.yaml
    │   ├── deployment-patch.yaml
    │   └── database-patch.yaml
    └── prod/
        ├── kustomization.yaml
        ├── deployment-patch.yaml
        └── database-patch.yaml

Creating Patches

Strategic Merge Patches

The most common approach - specify only the fields you want to change:

Base:

apiVersion: kubernetes.project-planton.org/v1
kind: PostgresKubernetes
metadata:
  name: app-database
spec:
  container:
    replicas: 1
    resources:
      limits:
        cpu: 500m
        memory: 1Gi
    diskSize: 10Gi

Prod Patch (overlays/prod/patch.yaml):

apiVersion: kubernetes.project-planton.org/v1
kind: PostgresKubernetes
metadata:
  name: app-database
spec:
  container:
    replicas: 3              # Override
    resources:
      limits:
        cpu: 2000m           # Override
        memory: 4Gi          # Override
    diskSize: 100Gi          # Override

Result: Base + patch merged = 3 replicas, 2000m CPU, 4Gi memory, 100Gi disk.

JSON 6902 Patches

For more complex changes:

overlays/prod/kustomization.yaml:

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

resources:
  - ../../base

patches:
  - target:
      kind: PostgresKubernetes
      name: app-database
    patch: |-
      - op: replace
        path: /spec/container/replicas
        value: 3
      - op: add
        path: /metadata/labels/environment
        value: production

Common Patterns

Pattern 1: Environment-Specific Resources

Different instance sizes per environment:

Dev (small, cheap):

spec:
  container:
    replicas: 1
    resources:
      limits:
        cpu: 500m
        memory: 512Mi

Prod (large, resilient):

spec:
  container:
    replicas: 5
    resources:
      limits:
        cpu: 2000m
        memory: 4Gi

Pattern 2: Environment-Specific Labels

Add labels for cost tracking:

overlays/prod/kustomization.yaml:

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

resources:
  - ../../base

commonLabels:
  environment: production
  cost-center: engineering
  team: backend

Pattern 3: Environment-Specific Images

overlays/dev/kustomization.yaml:

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

resources:
  - ../../base

images:
  - name: myapp/api
    newTag: latest          # Dev uses latest

patches:
  - path: dev-patch.yaml

overlays/prod/kustomization.yaml:

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

resources:
  - ../../base

images:
  - name: myapp/api
    newTag: v1.2.3          # Prod uses specific version

patches:
  - path: prod-patch.yaml

Pattern 4: Shared Configuration + Environment Patches

base/kustomization.yaml:

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

commonLabels:
  app: api
  managed-by: project-planton

resources:
  - deployment.yaml
  - database.yaml
  - cache.yaml

Each resource in base defines shared configuration, overlays patch for environment needs.


Workflow Examples

Deploying to Multiple Environments

# Deploy to dev
project-planton pulumi up \
  --kustomize-dir services/api/kustomize \
  --overlay dev \
  --yes

# Test in dev...

# Deploy to staging
project-planton pulumi up \
  --kustomize-dir services/api/kustomize \
  --overlay staging \
  --yes

# Test in staging...

# Deploy to production (with review)
project-planton pulumi preview \
  --kustomize-dir services/api/kustomize \
  --overlay prod

# Review changes...

project-planton pulumi up \
  --kustomize-dir services/api/kustomize \
  --overlay prod

Combining Kustomize with --set Overrides

# Kustomize overlay + runtime override
project-planton pulumi up \
  --kustomize-dir services/api/kustomize \
  --overlay prod \
  --set spec.container.image.tag=v1.2.4

Order of precedence:

  1. Base manifest
  2. Overlay patches applied
  3. --set overrides applied last (highest priority)

Preview Built Manifest

# See what Kustomize generates (useful for debugging)
cd services/api/kustomize
kustomize build overlays/prod

# Or let Project Planton build and show it
project-planton load-manifest \
  --kustomize-dir services/api/kustomize \
  --overlay prod

CI/CD Integration

GitHub Actions

name: Deploy API

on:
  push:
    branches:
      - main
      - develop

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Determine Environment
        id: env
        run: |
          if [ "${{ github.ref }}" == "refs/heads/main" ]; then
            echo "overlay=prod" >> $GITHUB_OUTPUT
          else
            echo "overlay=dev" >> $GITHUB_OUTPUT
          fi
      
      - name: Deploy
        run: |
          project-planton pulumi up \
            --kustomize-dir services/api/kustomize \
            --overlay ${{ steps.env.outputs.overlay }} \
            --yes
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

GitLab CI

deploy:
  script:
    - |
      if [ "$CI_COMMIT_BRANCH" == "main" ]; then
        OVERLAY="prod"
      elif [ "$CI_COMMIT_BRANCH" == "staging" ]; then
        OVERLAY="staging"
      else
        OVERLAY="dev"
      fi
    - project-planton pulumi up --kustomize-dir services/api/kustomize --overlay $OVERLAY --yes
  only:
    - main
    - staging
    - develop

Advanced Techniques

Multiple Bases

Useful for shared components:

common/
└── base/
    ├── kustomization.yaml
    └── shared-config.yaml

service-a/kustomize/
└── overlays/
    └── prod/
        ├── kustomization.yaml  # References ../../../common/base
        └── patch.yaml

service-b/kustomize/
└── overlays/
    └── prod/
        ├── kustomization.yaml  # Also references ../../../common/base
        └── patch.yaml

Components (Reusable Pieces)

components/monitoring/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component

patches:
  - path: add-monitoring.yaml

Use in overlay:

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

resources:
  - ../../base

components:
  - ../../../components/monitoring

Generating ConfigMaps from Files

overlays/prod/kustomization.yaml:

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

resources:
  - ../../base

configMapGenerator:
  - name: app-config
    files:
      - config/prod.yaml
      - config/secrets.encrypted

Troubleshooting

Error: "no such file or directory"

Problem: Kustomize can't find referenced files.

Solution:

# Check file paths in kustomization.yaml
# Ensure paths are relative to kustomization.yaml location

# Verify structure
ls -R services/api/kustomize/

Error: "kustomization.yaml not found"

Problem: Missing kustomization.yaml in overlay.

Solution:

# Create kustomization.yaml
cat > services/api/kustomize/overlays/prod/kustomization.yaml <<EOF
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base
EOF

Patch Not Applied

Problem: Your patch isn't affecting the final output.

Solution:

# 1. Verify patch is listed in kustomization.yaml
cat overlays/prod/kustomization.yaml

# 2. Check patch targets correct resource
# - apiVersion must match
# - kind must match
# - metadata.name must match

# 3. Test kustomize build directly
cd services/api/kustomize
kustomize build overlays/prod

Wrong Overlay Applied

Problem: Deployed dev config to prod (or vice versa).

Solution:

# Always verify overlay before deploying
project-planton pulumi preview \
  --kustomize-dir services/api/kustomize \
  --overlay prod  # Double-check this!

# Use explicit confirmation in CI/CD
if [ "$OVERLAY" != "prod" ]; then
  echo "Deploying to $OVERLAY"
  project-planton pulumi up --kustomize-dir ... --overlay $OVERLAY --yes
else
  echo "Production deployment - manual approval required"
  project-planton pulumi up --kustomize-dir ... --overlay $OVERLAY
fi

Best Practices

1. Keep Base Minimal

# ✅ Good: Base has common configuration
base/deployment.yaml:
  name: api
  container:
    image:
      repo: myapp/api
    # No environment-specific values

# ❌ Bad: Base has production values
base/deployment.yaml:
  name: api
  container:
    replicas: 10  # Production-specific, doesn't belong in base

2. One Overlay Per Environment

# ✅ Good
overlays/
├── dev/
├── staging/
└── prod/

# ❌ Confusing
overlays/
├── dev-us-west/
├── dev-eu-central/
├── staging-us-west/
└── ... (too many combinations)

3. Use Descriptive Patch Names

# ✅ Good
overlays/prod/
├── kustomization.yaml
├── resources-patch.yaml          # Increases resources
├── replicas-patch.yaml            # Scales replicas
└── monitoring-patch.yaml          # Adds monitoring

# ❌ Bad
overlays/prod/
├── kustomization.yaml
├── patch1.yaml
├── patch2.yaml
└── patch3.yaml

4. Version Control Everything

# ✅ Good: All kustomize files in Git
git add services/api/kustomize/
git commit -m "feat: add production overlay for API"

# ❌ Bad: Generated files, temp files committed
git add services/api/kustomize/overlays/prod/output.yaml  # Generated

5. Test Overlays Before Deploying

# ✅ Good: Preview before applying
kustomize build overlays/prod | less  # Review output
project-planton pulumi preview --kustomize-dir ... --overlay prod

# ⚠️ Risky: Deploy without review
project-planton pulumi up --kustomize-dir ... --overlay prod --yes

Complete Example

Here's a complete real-world example:

Directory Structure

backend/services/api/kustomize/
├── base/
│   ├── kustomization.yaml
│   ├── deployment.yaml
│   ├── database.yaml
│   └── redis.yaml
└── overlays/
    ├── dev/
    │   ├── kustomization.yaml
    │   ├── deployment-patch.yaml
    │   ├── database-patch.yaml
    │   └── redis-patch.yaml
    └── prod/
        ├── kustomization.yaml
        ├── deployment-patch.yaml
        ├── database-patch.yaml
        └── redis-patch.yaml

Files

base/kustomization.yaml:

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

commonLabels:
  app: api
  managed-by: project-planton

resources:
  - deployment.yaml
  - database.yaml
  - redis.yaml

base/deployment.yaml:

apiVersion: kubernetes.project-planton.org/v1
kind: MicroserviceKubernetes
metadata:
  name: api
spec:
  container:
    image:
      repo: mycompany/api
      tag: latest
    replicas: 1
    resources:
      limits:
        cpu: 500m
        memory: 512Mi

overlays/prod/kustomization.yaml:

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

resources:
  - ../../base

commonLabels:
  environment: production

images:
  - name: mycompany/api
    newTag: v1.0.0

patches:
  - path: deployment-patch.yaml
  - path: database-patch.yaml
  - path: redis-patch.yaml

overlays/prod/deployment-patch.yaml:

apiVersion: kubernetes.project-planton.org/v1
kind: MicroserviceKubernetes
metadata:
  name: api
spec:
  container:
    replicas: 5
    resources:
      limits:
        cpu: 2000m
        memory: 4Gi

Deployment

# Deploy to production
project-planton pulumi up \
  --kustomize-dir backend/services/api/kustomize \
  --overlay prod


Next Steps

  1. Create Your First Overlay: Start with a simple dev/prod split
  2. Test Locally: Use kustomize build to preview results
  3. Deploy Gradually: Test in dev before prod
  4. Iterate: Add more overlays as needed (staging, regional, etc.)

Kustomize + Project Planton gives you powerful multi-environment management with minimal duplication. Start simple and grow as needed.

Next article

Advanced Usage

Advanced Usage Guide Master advanced Project Planton techniques for power users and complex scenarios. Runtime Value Overrides with --set The --set flag lets you override manifest values without editing files. Think of it like command-line arguments for your infrastructure. Basic Syntax Nested Fields Use dot notation to access nested fields: Multiple Overrides Repeat the --set flag multiple times: When to Use --set ✅ Good use cases: Quick testing: "What if I used 10 replicas instead of 3?"...
Read next article