Argo CD Argo CD

Deploy Helm Charts and Kustomize Overlays per Environment

Structure one repository for dev and prod, and avoid the ConfigMap rollout trap.

15 min Intermediate

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 shows OutOfSync until it’s pruned. With prune: true enabled 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