In a helm install, what wins when values.yaml, two -f files and --set all set the same key?
answer
- Three layers, one merged map
- Files are ordered, flags come after
- Chart defaults sit at the bottom
- The --set family is applied last
basics
~20 sHelm starts from the chart's own values.yaml, merges each -f file in the order given so a later file beats an earlier one, and applies the --set family last. A --set value therefore wins over every file.
solid answer
~50 sHelm builds one merged values map before it renders anything, in three layers. The chart's own `values.yaml` is the base. Every `-f`/`--values` file is merged on top in the order the files appear on the command line, so `-f base.yaml -f prod.yaml` lets `prod.yaml` win. Then the `--set` family is merged last, which is why an inline `--set` beats any file. Merging is a deep merge for maps: an override file that sets only `resources.limits.cpu` leaves the rest of the `resources` map intact, so an override never has to restate the whole chart. Lists are the exception - an override list replaces the chart's list wholesale. To check the result rather than reason about it, render the chart locally with the same flags, or read back what a live release was given with `helm get values`.
code
bash · 5 lines# base.yaml, then prod-eu.yaml, then the flag - each layer beats the one before
helm install metrics ./monitoring-stack \
-f values/base.yaml \
-f values/prod-eu.yaml \
--set retentionHours=336go deeper
Be ready to recite the three layers in order: chart values.yaml, then each -f file left to right, then --set last. Say plainly that --set beats every file, and that maps merge rather than replace.
Explain the mechanics: precedence is by layer, not by command-line position, so --set wins even when typed first. Cover deep-merging of maps versus wholesale replacement of lists, and how you would verify a merge by rendering locally.
Show the operational judgment - how a wrapper script that reorders -f files silently diverges two environments, how a leftover --set in a CI job outranks the repository, and how you make the effective command and the rendered output visible before an apply.
Own the layering policy: how many override files a team may stack, whether --set is allowed in automated paths at all, and how values that decide production behaviour stay reviewable in Git rather than living in job arguments.
Every Helm render is driven by a single map of values, and every argument about "which setting actually won" is really an argument about how Helm built that map. It is built in three layers, always in the same order. ## The three layers, lowest precedence first 1. **The chart's `values.yaml`.** Each chart ships a `values.yaml` at its root holding the author's defaults. This is the base layer and the lowest precedence: it supplies a value for every key the templates expect, so that `helm install ./monitoring-stack` with no flags at all still renders. It is not a schema and not a whitelist; it is just data. 2. **The `-f` / `--values` files, in the order given.** `-f` is repeatable, and Helm merges the files onto the base one at a time, left to right, so the last file mentioned has the highest precedence among the files. This is what lets a team keep `values/base.yaml` with everything common and `values/prod-eu.yaml` with the handful of keys that region changes. The argument can be a local path or a URL. The ordering rule is easy to state and easy to get wrong in practice, because the order is usually decided by a wrapper script rather than by the person reading the diff. 3. **The `--set` family, last.** `--set` and its siblings are merged after every file, so an inline flag always wins. This is deliberate: the flags are the most specific, most situational input, typically a single key someone is changing for one run. It is also the reason a persistent "my values file is being ignored" report so often turns out to be a `--set` left in a CI job's arguments. ## Precedence is by layer, not by command-line position `helm install app ./chart --set replicaCount=3 -f prod.yaml` still lets `--set` win, even though the file was typed afterwards, because Helm merges all the value files first and then applies the set flags. Only the relative order of the `-f` files themselves matters. Likewise, the different `--set` variants are applied as a group rather than interleaved by position, so setting the same key twice with two different set flags is not something to reason about - set each key exactly once. ## How the layers combine What a later layer does to an earlier one depends on the kind of value it carries: | Override value | Merge behaviour | Effect on the earlier layer | | --- | --- | --- | | Map | Deep-merged key by key | Keys the override does not mention survive | | List | Replaced whole | Every element of the earlier list is gone | | `null` | Key deleted | The key is absent from the merged values | Deep merge by key is what makes small override files possible - a file containing only `resources: {limits: {cpu: 750m}}` does not wipe the memory limit the chart defaulted. Lists are replaced whole, never appended to or merged element by element, because list elements have no key to match on. And a key whose override value is `null` is deleted from the merged values rather than set to an empty value. ## Verify, do not reason The cheap habit is to render the chart locally with exactly the flags the real command will use and read the YAML that comes out, before touching a cluster. For a release that already exists, `helm get values` prints what was supplied for the current revision and `helm get manifest` prints the YAML those values produced. Together they turn "which layer won" from an argument into an observation. ## Where this goes wrong in real pipelines A CI wrapper that appends `-f` files in a loop puts the region file before the environment file for one job and after it for another, and two clusters diverge for reasons nobody can see in the repository. A key is misspelled at the top level of an override file - Helm accepts unknown keys silently unless the chart ships a `values.schema.json`, so the override does nothing at all and the chart default sails through. Someone adds a `--set` to debug an incident and never removes it, and from then on the values file for that key is decorative. In each case the fix is the same: make the full command visible, keep the number of layers small, and render before you apply.
- Does putting --set before the -f files on the command line change which one wins?No. Helm does not merge arguments in the order they were typed. It merges every `-f` file first, in the order the files were given, and then applies the `--set` family on top. `--set replicaCount=3 -f prod.yaml` still lets the `--set` win. Only the relative order of the `-f` files themselves affects the outcome.
- If two -f files both set a resources block, does the second file's block replace the first?No - maps are deep-merged. Keys present in the later file override the earlier one, and keys that only the earlier file set survive. So a small override file that sets `resources.limits.cpu` alone keeps the memory limit from the base file. Only lists are replaced wholesale, which is why a list override behaves so differently from a map override.
- How do you check what values a release that is already running was actually given?`helm get values <release>` prints the values supplied for the current revision, and `helm get manifest <release>` prints the YAML they rendered into. For a change you have not applied yet, render the chart locally with exactly the same `-f` and `--set` flags and diff that output against the manifest. That comparison answers precedence questions without guessing.
saying these in an interview costs you the question
- Says a -f file passed after --set overrides it
- Thinks only the last -f file is read at all
- Believes an override file must restate every chart default
- Says two values files concatenate their lists
- Claims the chart's values.yaml wins as the author's default
- Assumes Helm errors on an unknown key in an override file