One chart, many environments — values layering, Helmfile, and secret management
-f flags — later files override earlierHelm merges multiple -f files left to right. Later files override earlier ones at every key level.
# Base values + environment overlay
helm install myapp ./mychart \
-f values/base.yaml \
-f values/prod.yaml
# Three-layer pattern: base → env → region
helm upgrade myapp ./mychart \
-f values/base.yaml \
-f values/prod.yaml \
-f values/prod-us-east.yaml
Merge rule: Scalar values are replaced. Maps are deep-merged. Lists are replaced entirely (not appended). Design your values structure with this in mind.
replicaCount: 1
image:
repository: myapp
tag: latest
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
ingress:
enabled: false
featureFlags:
betaSearch: false
newCheckout: false
replicaCount: 3
image:
tag: "v2.4.1"
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
ingress:
enabled: true
hosts:
- host: app.example.com
paths: ["/"]
featureFlags:
newCheckout: true
Common environment deltas: replicas, resources, ingress hosts, image tags, feature flags, and log levels.
mychart/
Chart.yaml
values.yaml # defaults
values-dev.yaml
values-staging.yaml
values-prod.yaml
templates/
Simple. Works for a single chart with 2–3 environments.
deploy/
charts/
mychart/
values/
base.yaml
envs/
dev.yaml
staging.yaml
prod.yaml
regions/
us-east.yaml
eu-west.yaml
helmfile.yaml
Scales to many environments, regions, and services.
Helmfile wraps Helm with environment-aware, declarative release management.
# helmfile.yaml
repositories:
- name: bitnami
url: https://charts.bitnami.com/bitnami
environments:
dev:
values:
- values/envs/dev.yaml
staging:
values:
- values/envs/staging.yaml
prod:
values:
- values/envs/prod.yaml
releases:
- name: myapp
chart: ./charts/mychart
namespace: {{ .Values.namespace }}
values:
- values/base.yaml
- values/envs/{{ .Environment.Name }}.yaml
Key commands:
helmfile -e prod sync — install/upgrade all releases for prod.
helmfile -e prod diff — preview changes before applying.
helmfile -e prod apply — diff + sync only if changes exist.
# Inject from CI environment variables (never committed to Git)
helm upgrade myapp ./mychart \
-f values/base.yaml \
-f values/prod.yaml \
--set secrets.dbPassword="${DB_PASSWORD}" \
--set secrets.apiKey="${API_KEY}"
# Install the plugin
helm plugin install https://github.com/jkroepke/helm-secrets
# Encrypt a values file with age
sops --encrypt --age $(cat key.pub) values/prod-secrets.yaml > values/prod-secrets.enc.yaml
# Use encrypted file transparently
helm secrets upgrade myapp ./mychart \
-f values/base.yaml \
-f values/prod.yaml \
-f values/prod-secrets.enc.yaml
Both tools solve multi-environment configuration, but with different philosophies.
| Aspect | Helm Values | Kustomize |
|---|---|---|
| Approach | Templating + values merge | Patching raw YAML |
| Env management | Multiple -f files | Overlay directories |
| Packaging | Chart archives, OCI registries | Git directories |
| Best for | Reusable packages, app catalog | Last-mile customization |
Hybrid approach: Use helm template to render manifests, then apply Kustomize overlays for cluster-specific patches. ArgoCD supports this natively.
Pattern to remember: helm upgrade myapp ./mychart -f base.yaml -f $ENV.yaml --set secrets.key=$SECRET