Learning GitOps - FluxCD - Working with Kustomize
Episode 7 of 36

Learning GitOps - FluxCD - Working with Kustomize

Kustomize at scale: the structure of kustomization.yaml, the difference between the Flux Kustomization CRD and the Kustomize binary, overlay patterns for multiple environments, post-build customization, and advanced features.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 6 you deployed your first application through a Flux Kustomization. Now we go deeper: Kustomize as a templating language, and how Flux uses it at scale. This episode answers the question "how do I manage dozens of applications across many environments without duplicating YAML".

We'll dissect the kustomization.yaml structure, compare the Kustomization CRD with the Kustomize binary, learn the overlays pattern, then the post-build features and advanced features that only exist in Flux.

kustomization.yaml Basics

Kustomize is a tool that merges manifests without templating. A single kustomization.yaml file declares resources and transformations:

Basic kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
 
resources:
  - deployment.yaml
  - service.yaml
 
namePrefix: prod-
nameSuffix: -v2
 
commonLabels:
  app: webapp
  managed-by: flux
 
commonAnnotations:
  owner: platform-team
 
images:
  - name: nginx
    newTag: 1.27.3

Commonly used elements:

  • resources — the list of YAML files or other directories to merge.
  • namePrefix / nameSuffix — adds a prefix or suffix to all resource names so they don't collide across environments.
  • commonLabels / commonAnnotations — injects labels and annotations into all resources at once.
  • images — overrides image tags without modifying the original files.

Tip

Kustomize is purely declarative and idempotent: run it any number of times, and the result is the same. That's what makes it a better foundation for GitOps than drift-prone templating.

ConfigMap and Secret Generator

Instead of writing ConfigMaps by hand, use generators so Flux always refreshes their values when there's a change:

configMapGenerator and secretGenerator
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
 
configMapGenerator:
  - name: app-config
    files:
      - config/app.properties
    literals:
      - LOG_LEVEL=info
 
secretGenerator:
  - name: app-secret
    envs:
      - secret.env

Patches

Patches modify existing manifests without rewriting them. There are two styles: strategic merge and JSON patch:

Strategic merge patch
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
 
patches:
  - target:
      kind: Deployment
      name: webapp
    patch: |-
      spec:
        template:
          spec:
            containers:
              - name: webapp
                resources:
                  limits:
                    memory: 512Mi
JSON patch for list-based merge
[
  {
    "op": "add",
    "path": "/spec/template/spec/containers/0/env",
    "value": [
      {
        "name": "FEATURE_X",
        "value": "true"
      }
    ]
  }
]

Kustomization CRD vs the Kustomize Binary

Don't confuse them: Flux's kind: Kustomization is not the Kustomize binary. The Kustomize binary only renders YAML on the client side; the Flux CRD is a controller that runs Kustomize inside the cluster and reconciles continuously. The differences:

AspectKustomize BinaryFlux Kustomization CRD
Execution locationClient (laptop/CI)In-cluster controller
ReconciliationNone (one-shot)Continuous per interval
Health assessmentNoneBuilt-in: Deployment, Pod, Service, etc.
DependencyNonedependsOn between Kustomizations
PruneNoneprune: true for GC
OutputYAML filesApplied directly to the cluster

Flux features that don't exist in the binary:

Kustomization CRD with Flux features
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: apps
  namespace: flux-system
spec:
  interval: 10m
  timeout: 5m
  dependsOn:
    - name: infrastructure
  path: ./apps
  prune: true
  wait: true
  sourceRef:
    kind: GitRepository
    name: fleet
  • healthChecks — waits for resources to actually become healthy before considering it a success.
  • timeout — the limit for one reconciliation cycle (default 5 minutes).
  • dependsOn — other Kustomizations that must finish and be healthy first.

The Overlays Pattern

The most common way to manage many environments is the base + overlays pattern:

Base and overlays structure
apps/webapp/
├── base/
   ├── kustomization.yaml
   ├── deployment.yaml
   └── service.yaml
└── overlays/
    ├── dev/
   └── kustomization.yaml
    ├── staging/
   └── kustomization.yaml
    └── prod/
        └── kustomization.yaml

base holds the neutral manifests, while each overlay references the base and overrides the environment-specific parts:

overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
 
resources:
  - ../../base
 
namePrefix: prod-
commonLabels:
  env: prod
 
replicas:
  - name: webapp
    count: 4
 
images:
  - name: webapp
    newTag: 1.2.0

Then create a Flux Kustomization per environment:

Kustomization per environment
flux create kustomization webapp-prod \
  --source=fleet \
  --path="./apps/webapp/overlays/prod" \
  --prune=true \
  --interval=5m

Note

The DRY principle really applies: one deployment definition in base, and each environment only stores its delta. Major changes are done once in base and automatically spread to all overlays.

Post-Build Customization

Flux adds a layer beyond Kustomize: post-build variable substitution. This is covered in detail in episode 11, but the gist is written here because of its close tie to Kustomize:

postBuild on a Kustomization
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
spec:
  path: ./apps/webapp/overlays/prod
  postBuild:
    substitute:
      cluster: production
    substituteFrom:
      - kind: ConfigMap
        name: cluster-vars

The values from substitute and substituteFrom replace dollar-signed variables throughout the YAML before it's applied.

Warning

Remember the execution order: Flux runs the Kustomize build first (merge, patch, generators), then performs variable substitution on the result. So variables can be used in any field of the rendered manifests, but they can't change the structure of kustomization.yaml itself.

Advanced Kustomization Features

A few options that often come up in production:

  • force: true — replaces resources with immutable fields (e.g. Deployment selector label names). Use with care because Flux deletes then recreates the resource.
  • prune: true — garbage collection for resources that are no longer in Git. Required at the application level.
  • serviceAccountName — the Kustomization runs with a specific service account identity, important for tenancy (episode 12).
  • wait: true — Flux waits for all resources to become healthy before marking success, useful when other Kustomizations depend on it.
Example of advanced options usage
flux create kustomization webapp-prod \
  --source=fleet \
  --path="./apps/webapp/overlays/prod" \
  --prune=true \
  --force=true \
  --wait=true \
  --interval=5m

Closing

Kustomize together with Flux is the best way to keep your repository lean:

  • kustomization.yaml manages resources, generators, prefix/suffix, labels, and images.
  • The Flux Kustomization CRD adds reconciliation, health checks, dependency, and prune.
  • The base + overlays pattern separates common configuration from per-environment deltas.
  • Post-build substitution and advanced features close the gaps the Kustomize binary doesn't cover.

Your manifests can now scale. In episode 8 we'll manage Helm charts: HelmRepository as a source, HelmRelease for releases, values management strategies, and the install, upgrade, and rollback lifecycle. See you!