Skip to content

Writing Kustomize Overlays

Kustomize builds a final manifest set from a base (shared definitions) and one or more overlays (environment-specific additions and patches). You never modify the base to deploy to a specific environment. Instead, an overlay references the base and layers changes on top.

This project keeps infrastructure and applications separate, but both follow the same pattern:

infrastructure/
base/
kustomization.yaml # references monitoring/, traefik/
overlays/
local/
kustomization.yaml
traefik-patch.yaml
ingressroute-dashboard.yaml
ingressroute-grafana.yaml
apps/
base/
docs/
kustomization.yaml # namespace, deployment, service
overlays/
dev/
docs/
kustomization.yaml
ingressroute.yaml

A base kustomization.yaml lists the resources it owns. The infrastructure base references subdirectory names — Kustomize treats a directory name as a path to another kustomization.yaml:

infrastructure/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- monitoring
- traefik

The docs app base lists files directly:

apps/base/docs/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- deployment.yaml
- service.yaml

Neither base knows anything about the environment. They define the minimum set of resources every environment needs.

An overlay references its base with a relative path under resources, then lists any additional files alongside it:

infrastructure/overlays/local/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
- ingressroute-dashboard.yaml
- ingressroute-grafana.yaml
patches:
- path: traefik-patch.yaml

../../base resolves to infrastructure/base/, which Kustomize expands recursively. The two IngressRoute files are local to the overlay — they expose the Traefik dashboard and Grafana at *.k8s.local hostnames that only exist in the local cluster.

The docs overlay follows the same structure:

apps/overlays/dev/docs/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../../base/docs
- ingressroute.yaml

ingressroute.yaml adds a Traefik IngressRoute that routes docs.k8s.local to the docs service over TLS. The base has no IngressRoute at all — routing is an overlay concern because the hostname and TLS configuration differ per environment.

The patches field modifies resources that already exist in the base output. traefik-patch.yaml is a strategic-merge patch: it targets the HelmRelease named traefik and merges only the fields it specifies.

infrastructure/overlays/local/traefik-patch.yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: traefik
namespace: flux-system
spec:
values:
api:
dashboard: true
insecure: true
service:
type: ClusterIP
tlsStore:
default:
defaultCertificate:
secretName: mkcert-wildcard

Kustomize matches this patch to the existing HelmRelease by apiVersion, kind, and metadata.name. In the local environment, Traefik runs as ClusterIP (no external IP) and uses a locally-issued wildcard certificate. A production overlay would patch different values — or omit the patch entirely.

Run kubectl kustomize to preview the merged output, then pipe it to apply:

Terminal window
# Preview what Kustomize will produce
kubectl kustomize infrastructure/overlays/local
# Apply directly
kubectl apply -k infrastructure/overlays/local

Flux can also apply a kustomization automatically by pointing a Kustomization resource at an overlay path in the repository.

  • Never modify the base for a specific environment. All environment differences belong in an overlay.
  • Overlays reference bases by relative path. Use ../../base or ../../../base/docs — the path must resolve to a directory containing a kustomization.yaml.
  • Patches target by identity. A strategic-merge patch must include apiVersion, kind, metadata.name, and metadata.namespace to match the right resource.
  • Additional resources are overlay-local. Files like ingressroute.yaml live next to the overlay’s kustomization.yaml and are not visible to the base or other overlays.