Structure a Repository with Kustomize Overlays per Environment
Organize one repository for dev and production, and avoid the ConfigMap rollout trap.
Real applications run in several environments that differ in small ways: replica counts, hostnames, configuration values. Flux applies plain YAML or Kustomize overlays, and detects which one a folder uses. This guide shows how to structure a repository for multiple environments with Kustomize, how to tell Flux about each one, and covers a pitfall that catches people with configuration changes. It builds on the webapp example from earlier guides.
Choose a layout: folders, not branches
You can model environments as long-lived branches (dev, staging, main), but that makes promotion a merge exercise and lets environments drift apart. A single branch with one folder per environment is easier to reason about: every environment is visible side by side, and a change to one is a normal diff.
Flux’s documentation describes three common repository strategies:
- Monorepo. All clusters, apps, and infrastructure in one repository. Easiest to start with and the one used here.
- Repository per environment. For example, one for staging and one for production, with separate access rules.
- Repository per team or per application. Strong isolation, with a platform repository that registers each team’s repository.
A typical monorepo looks like this:
fleet-infra/
├── clusters/
│ ├── dev/ # Flux Kustomizations for the dev cluster
│ └── production/ # Flux Kustomizations for the production cluster
├── infrastructure/ # ingress controller, cert-manager, monitoring
└── apps/
├── base/webapp/ # shared manifests
├── dev/ # overlay for dev
└── production/ # overlay for production
Build a Kustomize base and overlays
apps/base/webapp/kustomization.yaml lists the shared resources:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
In apps/base/webapp/deployment.yaml, have the container read its settings from a ConfigMap that each overlay will generate (leave the namespace field out of the base, since overlays set it):
envFrom:
- configMapRef:
name: webapp-config
apps/dev/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: webapp-dev
resources:
- namespace.yaml
- ../base/webapp
replicas:
- name: webapp
count: 2
configMapGenerator:
- name: webapp-config
literals:
- HEADER=Hello from dev
with a namespace.yaml that creates webapp-dev. apps/production/kustomization.yaml looks the same, with namespace: webapp-prod, count: 4, its own namespace manifest, and its own HEADER value. You can check what an overlay renders before committing:
kustomize build apps/dev
# or, without installing kustomize separately:
kubectl kustomize apps/dev
Point a Flux Kustomization at each overlay
Flux detects the kustomization.yaml in the path and builds it for you. In each cluster’s folder, add one Flux Kustomization per environment. clusters/dev/apps.yaml:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: flux-system
path: ./apps/dev
prune: true
wait: true
timeout: 3m
For the production cluster, clusters/production/apps.yaml uses path: ./apps/production. Because each cluster was bootstrapped with its own --path, each cluster only sees its own folder.
Make production deliberate
Unlike tools with a manual sync button, Flux applies automatically, so you gate production through Git. One strong pattern is to let dev follow a branch and let production follow released tags. In the production cluster’s source:
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: fleet-releases
namespace: flux-system
spec:
interval: 5m
url: https://github.com/your-org/fleet-infra
ref:
semver: ">=1.0.0"
Production then only changes when you tag a release. Pull request approval on the main branch is the other common gate.
Handle configuration changes on purpose
Change the HEADER value in one environment and see what happens. Kustomize appends a hash of the content to generated ConfigMap names. A new value produces a new ConfigMap (webapp-config-<hash>) and updates the Deployment’s reference, which triggers a Pod rollout. The old ConfigMap is now orphaned, and with prune: true Flux deletes it for you. That behavior is exactly what you want: config changes roll the Pods.
Plain, hand-written ConfigMaps are different. If you edit a ConfigMap that a Deployment reads through envFrom, the Deployment spec doesn’t change, so running Pods keep the old values. Use configMapGenerator for anything that should restart workloads when it changes. Helm charts have an equivalent technique, covered in the Helm guide.
Substitute per-cluster values
Sometimes a value differs per cluster but you don’t want a whole overlay for it, for example a domain name. Flux Kustomizations can substitute variables after the build:
spec:
postBuild:
substitute:
cluster_domain: dev.example.com
substituteFrom:
- kind: ConfigMap
name: cluster-vars
Any ${cluster_domain} in your manifests is replaced at apply time. Use it sparingly, since overlays are easier to review.
Promote changes through environments
With this layout, promotion is a sequence of ordinary commits. Change the dev overlay first (for example, an image bump with kustomize edit set image nginx=nginx:1.26 run in apps/dev), let Flux apply it, and verify. Then make the same change in apps/production, through a pull request that requires approval. A CI pipeline can automate the dev commit, and a reviewer gates the production one. Flux’s image automation can also do the dev step for you, which the image automation guide covers.
Popular Guides
-
Push vs Pull: How Flux Delivers to Kubernetes Why pull-based, Git-driven delivery changes how you deploy, secure, and recover clusters. -
Deploy Your First Application with a GitRepository and Kustomization Point Flux at manifests in Git using a Kustomization resource and watch it sync. -
Control Sync Behavior: Intervals, Prune, Drift Correction, and Rollback Decide when Flux applies changes, what it deletes, how to pause it, and how to undo a bad deploy. -
Automate Image Updates with Flux Image Automation Let Flux detect new container image tags and commit them back to Git. -
Configure Multi-Cluster with Flux Manage several clusters from one repository, with shared infrastructure and per-cluster differences. -
Flux CD vs Argo CD: Choosing a GitOps Tool Compare architecture, UI, Helm, secrets, tenancy, and image automation to pick the right tool.