Deploy Helm Charts and Kustomize Overlays per Environment
Structure one repository for dev and prod, and avoid the ConfigMap rollout trap.
Real applications run in several environments that differ in small ways: replica counts, hostnames, configuration values. Argo CD can deploy from plain YAML, Helm charts, or Kustomize, and detects which one a folder uses. This guide shows how to structure one repository for multiple environments with Kustomize overlays and with Helm values files, 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.
Kustomize is built for this: a shared base plus a small overlay per environment that changes only what differs.
Build a Kustomize base and overlays
webapp-config/
├── base/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── kustomization.yaml
└── overlays/
├── dev/
│ └── kustomization.yaml
└── prod/
└── kustomization.yaml
base/kustomization.yaml lists the shared resources:
resources:
- deployment.yaml
- service.yaml
In base/deployment.yaml, have the container read its settings from a ConfigMap that each overlay will generate:
envFrom:
- configMapRef:
name: webapp-config
overlays/dev/kustomization.yaml:
resources:
- ../../base
namespace: webapp-dev
replicas:
- name: webapp
count: 2
configMapGenerator:
- name: webapp-config
literals:
- HEADER=Hello from dev
overlays/prod/kustomization.yaml looks the same, with namespace: webapp-prod, count: 4, and its own HEADER value.
Point one Application at each overlay
Argo CD detects a kustomization.yaml in the path and builds it for you. Create one Application per environment:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: webapp-dev
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/your-org/webapp-config.git
targetRevision: HEAD
path: overlays/dev
destination:
server: https://kubernetes.default.svc
namespace: webapp-dev
syncPolicy:
syncOptions:
- CreateNamespace=true
automated:
prune: true
For webapp-prod, change the name and path (overlays/prod) and the namespace, and consider leaving automated out so production syncs are approved by a person.
For Helm, the shape is the same, but the source is a chart and you select an environment’s values file:
source:
repoURL: https://github.com/your-org/webapp-config.git
targetRevision: HEAD
path: charts/webapp
helm:
valueFiles:
- values-dev.yaml
valueFiles paths are relative to the chart folder, and later files override earlier ones.
Handle configuration changes on purpose
Change the HEADER value in one environment and compare what each tool does:
- 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, so the application showsOutOfSyncuntil it’s pruned. Withprune: trueenabled this is cleaned up for you. - Helm updates the existing ConfigMap in place. The Deployment spec doesn’t change, so the running Pods keep the old values and nothing tells Argo CD or Kubernetes to restart them.
The standard fix for Helm is to put a checksum of the ConfigMap into the Pod template, so a config change changes the template and rolls the Pods. In templates/deployment.yaml:
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
Restarting the Deployment by hand also works once, but it isn’t captured in Git, which defeats the purpose.
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 webapp=nginx:1.26 run in overlays/dev), let automated sync apply it, and verify. Then make the same change in overlays/prod and sync it, manually if you’ve kept production gated. A CI pipeline can automate the dev commit, and a pull request or approval can gate the production one. To operate many clusters or environments from one place, look at Argo CD ApplicationSets, which generate Application resources from a template.
Popular Guides
-
Push vs Pull: How Argo CD Delivers to Kubernetes Why pull-based, Git-driven delivery changes how you deploy, secure, and recover clusters. -
Configure Multi-Cluster with Argo CD Manage deployments across multiple clusters. -
Deploy Your First Application with a Declarative Manifest Point Argo CD at a Git repository using an Application resource and watch it sync. -
Control Sync Behavior: Auto-Sync, Prune, Self-Heal, and Rollback Decide when Argo CD applies changes, what it deletes, and how to undo a bad deploy.