Designing for Multiple Environments
How do you manage different environments (Dev, Staging, Prod) in Git? Three common approaches exist:
- Branch-per-environment:
devbranch,stagingbranch,mainbranch. Simple to understand, but merging between branches is error-prone and Git history becomes hard to follow. - Repo-per-environment: Separate Git repos for each environment. Clean isolation, but duplicates configuration and makes cross-environment changes tedious.
- Directory-per-environment (recommended): A single repo with a directory structure that separates shared base configuration from environment-specific overrides, using Kustomize.
The directory-per-environment pattern is the most widely adopted because it keeps all configuration in one place while making environment differences explicit and reviewable.
Kustomize: Base and Overlays
Kustomize is a Kubernetes-native configuration management tool (built into kubectl). It works by defining a base — the common configuration shared across all environments — and overlays that patch the base for each specific environment.
gitops-repo/
├── apps/
│ └── payment-api/
│ ├── base/ # Shared across all environments
│ │ ├── deployment.yaml # image, ports, env vars (non-secret)
│ │ ├── service.yaml
│ │ └── kustomization.yaml # Lists resources in base
│ └── overlays/
│ ├── dev/
│ │ ├── kustomization.yaml # References base, applies dev patches
│ │ └── patch-replicas.yaml # replicas: 1
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ └── patch-replicas.yaml # replicas: 2
│ └── prod/
│ ├── kustomization.yaml
│ └── patch-replicas.yaml # replicas: 5
└── clusters/
├── dev-cluster/
│ └── payment-api.yaml # ArgoCD Application → overlays/dev
├── staging-cluster/
│ └── payment-api.yaml # ArgoCD Application → overlays/staging
└── prod-cluster/
└── payment-api.yaml # ArgoCD Application → overlays/prod
The clusters/ directory contains the ArgoCD Application or Flux Kustomization CRDs that tell each cluster's GitOps agent which path to watch. The dev cluster's agent watches overlays/dev; the prod cluster's agent watches overlays/prod.
Promoting changes from dev to prod is a Pull Request that updates the prod overlay — no special tooling, no pipeline magic. The PR is the promotion mechanism, and Git history is the promotion audit trail.
The base's kustomization.yaml simply lists the resources that make up the shared configuration (the Deployment and Service). The prod overlay's kustomization.yaml references that base directory, layers on its replica patch, and uses an images transformer to pin the container to a specific tagged version rather than inheriting whatever the base points at — prod runs a fixed release, not a floating tag.