Skip to content

Kustomize Build Check Action

Intelligent validation of Kustomize configurations in GitHub Actions with automatic dependency tracking and smart change detection.

The Problem

In GitOps workflows with dozens or hundreds of Kustomize overlays, pull request validation becomes a challenge:

  • Manual testing is error-prone: "Did I remember to check all affected overlays?"
  • Testing everything is slow: kustomize build on 50 directories takes time
  • CI fails are opaque: "Build failed somewhere, good luck finding it"

You need to know:

  • Which overlays are affected by a base change?
  • Did my patch break downstream configurations?
  • Are all dependent overlays still valid?

Traditional approaches either test nothing (risky) or test everything (slow). Neither scales well.

The Solution

This action intelligently validates only what's affected by your changes:

  1. Auto-discovers all kustomization files in your repository
  2. Builds a dependency graph to understand base → overlay relationships
  3. Detects changed files via git diff against the base branch
  4. Tests affected kustomizations - bases and all their dependents
  5. Reports clear results with execution time and failure details

When you change manifests/app/base/kustomization.yaml, it automatically tests:

  • The base itself
  • All overlays that reference it
  • Any overlays that reference those overlays (recursive)

Key Capabilities

  • Smart dependency analysis: Understands multi-level overlay dependencies
  • Helm support: Built-in support for helmCharts in kustomization files
  • GitHub Actions integration: Outputs for downstream steps, annotations on failures
  • Monorepo friendly: Specify root-dir to validate specific subdirectories
  • Configurable failure behavior: Continue on error or fail fast

Quick Example

Basic Pull Request Validation

yaml
name: Validate Kustomize

on:
  pull_request:
    paths:
      - '**/*.yaml'
      - '**/*.yml'

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0  # Required for git diff
      
      - uses: michielvha/kustomize-build-check-action@main
        with:
          enable-helm: true
          fail-on-error: true
          base-ref: ${{ github.event.pull_request.base.sha || 'HEAD~1' }}

This validates all affected configurations in every PR, catching issues before merge.

Monorepo with Multiple Apps

yaml
jobs:
  validate-app1:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      
      - uses: michielvha/kustomize-build-check-action@main
        with:
          root-dir: ./apps/app1/manifests
          enable-helm: true
  
  validate-app2:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0
      
      - uses: michielvha/kustomize-build-check-action@main
        with:
          root-dir: ./apps/app2/manifests
          enable-helm: true

Each app gets independent validation with its own dependency graph. Can speed up the process when using large repositories

Continue on Error for Reporting

yaml
- name: Validate Kustomize (non-blocking)
  uses: michielvha/kustomize-build-check-action@main
  with:
    fail-on-error: false  # Don't fail the workflow
    enable-helm: true
  
- name: Check validation results
  if: always()
  run: |
    echo "Total builds: ${{ steps.validate.outputs.total }}"
    echo "Successful: ${{ steps.validate.outputs.successful }}"
    echo "Failed: ${{ steps.validate.outputs.failed }}"

Use outputs to build custom reporting or notifications without blocking the pipeline.

How It Works

Dependency Graph Construction

The action discovers all kustomization files and builds a dependency graph:

manifests/
├── app/base/                    [BASE]
│   └── kustomization.yaml  
│       (used by dev, staging, prod)
└── app/overlays/
    ├── dev/                     [OVERLAY]
    │   └── kustomization.yaml   (references: ../../base)
    ├── staging/                 [OVERLAY]
    │   └── kustomization.yaml   (references: ../../base)
    └── production/              [OVERLAY]
        └── kustomization.yaml   (references: ../../base)

Graph representation:

base → [dev, staging, production]

Impact Analysis

When manifests/app/base/kustomization.yaml changes:

  1. Detects the changed file is a kustomization
  2. Finds the kustomization directory: manifests/app/base
  3. Looks up all dependents in the graph: [dev, staging, production]
  4. Tests all affected: base + dev + staging + production

Result:

✅ manifests/app/base - Build successful (0.05s)
✅ manifests/app/overlays/dev - Build successful (0.12s)
✅ manifests/app/overlays/staging - Build successful (0.11s)
✅ manifests/app/overlays/production - Build successful (0.13s)

Summary: 4 total, 4 successful, 0 failed

Recursive Dependencies

Handles multi-level overlays correctly:

base → overlay1 → overlay2

When base changes, tests: base, overlay1, AND overlay2 (recursive).

Configuration Reference

Inputs

InputDescriptionDefaultRequired
enable-helmEnable Helm chart supporttrueNo
fail-on-errorFail workflow if builds failtrueNo
base-refGit ref to compare againstHEAD~1No
root-dirDirectory to search for kustomizations.No

Outputs

OutputDescription
totalTotal kustomizations tested
successfulNumber of successful builds
failedNumber of failed builds

Advanced Usage

Custom Base Ref for Pull Requests

yaml
with:
  base-ref: ${{ github.event.pull_request.base.sha || 'HEAD~1' }}

This ensures you test ALL changes in the PR, not just the last commit:

  • With base.sha: Compares entire PR branch against base (e.g., main)
  • Without it (HEAD~1): Only compares last commit against previous commit

If your PR has 3 commits that change different files, HEAD~1 would only test the last commit's changes!

Kubeconform Validation

Combine with kubeconform for schema validation:

yaml
- name: Validate Kustomize
  uses: michielvha/kustomize-build-check-action@v1
  with:
    fail-on-error: false
  id: kustomize

- name: Schema validation with kubeconform
  if: steps.kustomize.outputs.failed == '0'
  run: |
    # Only run kubeconform if kustomize builds succeeded
    for path in ${{ steps.kustomize.outputs.paths }}; do
      kustomize build $path | kubeconform -strict
    done

Matrix Strategy for Large Repos

yaml
strategy:
  matrix:
    app: [app1, app2, app3, app4]
  fail-fast: false

steps:
  - uses: michielvha/kustomize-build-check-action@v1
    with:
      root-dir: ./manifests/${{ matrix.app }}

Parallel validation across multiple applications.

Why This Matters

Developer Experience

Developers shouldn't need to understand the entire dependency tree. They make a change, open a PR, and get immediate feedback on what broke.

No guessing which overlays to test manually. No waiting for production to discover the issue.

GitOps Best Practices

This action enforces validation before merge. Your main branch stays clean, and your deployments stay reliable.

Combined with Argo CD or Flux, you get a complete GitOps pipeline:

  1. Change manifests
  2. Open PR
  3. Automated validation (this action)
  4. Merge
  5. Auto-deploy

Architecture

The action is backed by a dual-repository architecture:

This separation provides:

  • Clean user interface (action repo)
  • Reusable tool for other CI/CD systems (tool repo)
  • Container-based execution for consistency
  • Independent versioning

Security

The action runs in a Docker container as a non-root user (UID 1001) matching GitHub Actions runner permissions.

Git operations use system-level safe.directory configuration to handle mounted volumes:

dockerfile
RUN git config --system --add safe.directory '*'

No credentials are stored or transmitted. The action only needs repository read access.

Resources