Right, so you’ve got your Git repository set up as your source of truth—your single, glorious, version-controlled system of record. But let’s be honest, you’re probably not deploying the exact same YAML to every environment. You need to change the number of replicas in production, or swap out a config map for staging. This is where Kustomize struts in, and Flux’s Kustomization resource is how you make it dance.

Think of a Kustomization (the Flux kind, capitalized, we’ll get to that) as the conductor of your deployment orchestra. It doesn’t hold the musical scores itself; it points to a directory in your Git repo that contains your kustomization.yaml (the Kustomize kind, lowercase, yes it’s confusing) and then it tells Flux, “Hey, go to this git repository, grab everything in this folder, run kustomize build on it, and apply the resulting YAML to the cluster.” It’s the crucial link between your fancy, overlayed manifests and the cluster they’re supposed to run on.

The Anatomy of a Flux Kustomization

Here’s a basic but fully functional example. You’ll apply this YAML to your cluster, and Flux will take it from there.

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: my-app-production
  namespace: flux-system
spec:
  interval: 5m0s # How often to reconcile from Git
  path: ./apps/my-app/overlays/production # The path to the kustomization.yaml dir
  prune: true # Holy crap, this is important. It garbage collects resources removed from Git.
  sourceRef:
    kind: GitRepository # References the GitRepository source you already created
    name: my-infra-repo # The name of that source object
  validation: client # Uses `kubectl` for server-dry-run validation before applying
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: my-app
      namespace: production

Let’s break down the critical bits. The interval is your pull frequency; it’s how often Flux says, “Anything new for me?” The path is everything. This must point to the directory containing your kustomization.yaml file, not the file itself. prune: true is non-optional for any real usage—it ensures that if you delete a manifest from Git, Flux will delete it from the cluster. This is GitOps, after all. The sourceRef is how this Kustomization knows which Git repository to even look at.

Why Prune is Your Best Friend and Worst Enemy

I need to stop and make you look at prune: true again. This is the feature that makes GitOps so powerful and so terrifying. It means your cluster state exactly matches your Git state. Delete a file? Flux will delete the resource. It’s brilliant. It’s also a fantastic way to accidentally delete a critical, stateful service if you mess up your kustomization.yaml patches. Always, always use validation: client and test your changes in a non-production environment first. Flux’s power demands respect.

Taming the Overlay Beast

Your Git repo structure should be sane. A common and effective pattern is:

├── apps/
│   └── my-app/
│       ├── base/
│       │   ├── kustomization.yaml
│       │   ├── deployment.yaml
│       │   └── service.yaml
│       └── overlays/
│           ├── staging/
│           │   ├── kustomization.yaml  # patches replicas, uses staging configmap
│           │   └── patch_replicas.yaml
│           └── production/
│               ├── kustomization.yaml # patches replicas, ingress, resource limits
│               ├── patch_replicas.yaml
│               └── patch_resources.yaml
└── infrastructure/
    └── redis/
        └── ... 

Your Flux Kustomization for production would point to ./apps/my-app/overlays/production. It will build that overlay, which in turn references the base (or another overlay), and the result is what gets deployed. This keeps your environment-specific changes isolated and obvious.

When Things Go Sideways: Dependency Management

Here’s a fun “gotcha.” You have a Kustomization that deploys a Namespace and another that deploys an Application in that namespace. What happens if Flux tries to apply the App before the Namespace is ready? It fails. Spectacularly.

This is where dependsOn comes in. You can explicitly tell one Kustomization to wait for another.

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: my-app-production
  namespace: flux-system
spec:
  interval: 5m0s
  path: ./apps/my-app/overlays/production
  prune: true
  sourceRef:
    kind: GitRepository
    name: my-infra-repo
  dependsOn:
    - name: my-namespace-production # Wait for this Kustomization to be healthy first

This is crucial for bootstrapping infrastructure in the correct order. Without it, you’re just hoping the cluster is feeling cooperative that day. Don’t hope. Define your dependencies.

Drift Detection and Self-Healing

The real magic isn’t just the deployment. It’s the reconciliation loop. Every five minutes (or whatever your interval is), Flux will:

  1. Pull the latest code from your specified branch.
  2. Run kustomize build on the specified path.
  3. Calculate a diff between the desired state (the build output) and the actual state of the cluster.
  4. Apply any changes necessary to make the cluster match Git.

If someone goes rogue and kubectl edit’s a deployment to add a “debug” sidecar, Flux will spot the drift on its next run and revert the change to whatever is defined in Git. The cluster defends itself from configuration drift. This is the core superpower you signed up for. Use it wisely.