skip to content

How do you run one Helm chart across dev, staging and production without forking it?

level: juniorimportance: must knowfreq 68%

answer

  1. The chart itself knows no environments
  2. Defaults in the chart, differences outside it
  3. One small file per environment
  4. Passed at deploy time with -f
  5. Only the keys that actually differ

basics

~20 s

Keep one chart whose values.yaml holds the defaults true everywhere, then pass a small per-environment file at deploy time: helm upgrade -f values/prod.yaml. Only keys that genuinely differ live in that file; the chart is identical everywhere.

solid answer

~40 s

The chart is environment-agnostic: `values.yaml` carries the defaults that hold everywhere — port, probe paths, labels, the shape of the config — and each environment gets its own committed file holding only what differs, such as replica counts, resource requests, hostnames and feature toggles. The deploy is then `helm upgrade --install fraud-platform charts/fraud-platform -n fraud-prod -f values/prod.yaml`, and the same command with a different `-f` file serves staging. In an umbrella chart the keys for each subchart nest under that subchart's name, so an environment file for an API-plus-worker chart sets `api:` and `worker:` blocks. Keep durable configuration in the file rather than in ad-hoc `--set` arguments, because the file is reviewable and reproducible; Helm records the supplied values with the release, so `helm get values` shows exactly what an environment was given.

code

bash · 7 lines
bash
helm upgrade --install fraud-platform charts/fraud-platform \
  --namespace fraud-staging --create-namespace \
  -f values/staging.yaml

helm upgrade --install fraud-platform charts/fraud-platform \
  --namespace fraud-prod --create-namespace \
  -f values/prod.yaml

go deeper

for a junior

Be ready to say the shape out loud: one chart, defaults in its values.yaml, one small file per environment passed with -f at deploy time. Know that -f can be given more than once and that the file holds only the keys that differ.

for a middle

Explain what belongs in chart defaults versus an environment file, why the environment files sit outside the packaged chart, and why Helm records the supplied values with the release. Be able to show the umbrella-chart case where a subchart's keys nest under its name.

for a senior

Show the operating discipline: files that stay diffable, every environment rendered in CI rather than only the one being deployed, no credentials in values, and no environment names leaking into templates where a config reviewer will never see them.

for a principal

Own the boundary question — which team owns chart defaults, which owns the environment files, and what stops the environment files becoming three unrelated documents. Be ready to argue why the environment axis belongs in values rather than in template branches.

## One chart, many environments A Helm chart is a directory holding `Chart.yaml`, a `templates/` directory of Go templates, and a `values.yaml` of defaults those templates read through `.Values`. Nothing in that structure is environment-aware. The chart describes *a* deployment of your application, not the dev one or the prod one; the environment axis lives outside the chart, in the values handed to it at install time. That is the whole design: templates plus values, with the values being the part that varies. So the standard shape is a single chart plus one small values file per environment, passed with `-f`: ``` charts/fraud-platform/ # the chart: templates + values.yaml defaults values/dev.yaml values/staging.yaml values/prod.yaml ``` and a deploy that differs only in which file it names. ## What goes where **Chart defaults** carry everything that is true of every environment: the container port, probe paths and thresholds, label and annotation conventions, the Service type, the structure of the application config, and safe starting values for the knobs. A newcomer should be able to `helm install` the chart with no `-f` at all and get something that runs. **The environment file** carries only the difference: replica counts, CPU and memory requests, autoscaler bounds, the ingress hostname, the storage class, the addresses of external systems, and feature toggles. For an umbrella chart bundling a fraud-scoring API and a worker as subcharts, the environment file nests those keys under each subchart's name — `api:` and `worker:` blocks — because that is how a parent chart addresses a subchart's values. The environment files are **not** part of the packaged chart. A packaged chart is meant to be shared and promoted unchanged; the environment files belong to whoever operates the deployment and live in the repository that owns it. Baking `values/prod.yaml` into the tarball would mean shipping your hostnames to everyone who pulls the chart, and would mean a config change forces a new chart version. ## Why a file rather than --set Anything durable belongs in a committed file. A pile of `--set` arguments assembled by a deploy script is invisible in review, hard to reproduce by hand, and easy to forget on the next upgrade. Helm stores the values it was given alongside the release, so `helm get values fraud-platform -n fraud-prod` prints what that environment actually received — which is only useful if that content also exists in a file someone can read before the deploy. The same storage fact is a warning about secrets. A values file is committed to git, and the release record is itself a Kubernetes Secret in the namespace, readable by anyone with get access to Secrets there. Credentials therefore do not belong in `values/prod.yaml` or in `--set`; the chart should reference an existing Secret by name, and something else — an external secret operator, or the deploy job — should put the material in it. ## The two anti-patterns **A chart per environment.** Copying `charts/fraud-platform` to `charts/fraud-platform-prod` and editing it means three sets of templates to review, three places for a fix to be forgotten, and no way to say that staging tested what production will run. Every difference that was supposed to be a value becomes a code difference. **Environment names inside templates.** `{{ if eq .Values.environment "prod" }}` moves the difference from the values files, where a reviewer diffing three short documents can see it, into the template body, where nobody looks during a config review. It also makes the chart untestable for anyone whose environments are not named yours. Express the difference as an ordinary value with a default instead, and let each environment set it. ## Keeping the files comparable The practical discipline is that the three files should be diffable. Keep the same keys present in every environment's file even where the value repeats, so a missing key is visible rather than silently inherited; keep the files short by pushing anything constant back into the chart; and check all of them in CI rather than only the one being deployed — rendering each with `helm template charts/fraud-platform -f values/<env>.yaml` catches a file that has drifted into something that no longer renders long before it is anyone's deploy.

  • Should the per-environment values files live inside the chart directory?
    No. The chart is the shared, promotable artifact; the environment files describe your particular installation. Keeping them outside means the same packaged chart can be pulled and installed by anyone, your hostnames are not shipped inside it, and a configuration change does not force a new chart version. They belong in the repository that owns the deployment, usually under a `values/` directory next to the deploy job that names them.
  • A value must differ per environment but is a credential. Where does it go?
    Not in the values file and not in `--set`. The values file is committed, and Helm stores the supplied values in the release record — itself a Secret in the namespace — so anyone who can read Secrets there can print it back. Have the chart reference an existing Kubernetes Secret by name, let the environment file supply that name, and let an external secret operator or the deploy process populate the contents.
  • How do you check what an environment was actually deployed with?
    `helm get values fraud-platform -n fraud-prod` prints the values supplied to the current revision, and adding `--revision` shows an older one. Compare that with the file you expected to be used: if they disagree, someone deployed with different arguments than the repository records, which is the usual source of an environment nobody can reproduce.

The chart is a recipe and the environment file is the note stuck to it: same dish everywhere, written down once, with 'double the quantities and use the big oven' for the one occasion that needs it.

saying these in an interview costs you the question

  • Copies the chart per environment and edits each copy
  • Puts environment-name conditionals inside templates
  • Deploys with a long --set list nobody can reproduce
  • Assumes prod is fine because staging rendered
  • Commits database passwords into values/prod.yaml
  • Thinks environment files ship inside the packaged chart

context