Sign in
← Kubernetes: Kustomize and Helm
5 min · easy · Lesson 8 of 9

Helm: a values file instead of --set

The order values are layered in, how several files get merged, and the rules Helm uses to merge dicts and lists.

Why

The same chart gets installed into several environments: stage, prod, a local cluster. They differ in settings: replica count, image tag, Ingress hosts. Keeping those differences in a long --set command is awkward, hard to repeat, and can't go into git. A values file solves both problems.

Concepts

Every chart has a values.yaml with default settings. You override them two ways: the --set flag right in the command, or a separate file via -f (long form --values).

A values file is plain YAML with the same structure as the chart's values.yaml. Put into it only what differs from the default:

# myvalues.yaml
replicaCount: 2
image:
  repository: nginx
  tag: "1.27.3-alpine"
resources:
  limits:
    memory: 256Mi

The file is passed on install or upgrade:

helm install web ./myapp -f myvalues.yaml -n demo

To see which keys a chart even understands, check its defaults:

helm show values ./myapp

How it works

Order of priority

Values are layered, and each next layer overrides the previous one:

  1. values.yaml inside the chart.
  2. Files from -f, left to right.
  3. --set, --set-string, --set-file.

That gives a practical rule: --set beats everything, and among several -f files the last one wins.

Several files are used to separate what's shared from what's specific:

helm upgrade --install web ./myapp -n demo \
  -f values/common.yaml \
  -f values/prod.yaml

common.yaml holds what's the same across all environments, prod.yaml holds only prod's differences. The second file sits on top of the first.

How values merge

Dicts merge deeply. If common.yaml sets image.repository and prod.yaml sets only image.tag, both keys survive.

Lists get replaced entirely. It's easy to slip here: the ingress.hosts array from the second file doesn't add to the array from the first, it replaces it. To get a union, you have to spell out the whole list in the top file.

You can remove a key set further down the chain by assigning it null:

# prod.yaml
podAnnotations: null

An empty dict {} doesn't work that way. It merges with the dict below by the usual rule, and every key below stays in place. null removes the key from the final set of values, and the template sees it as absent.

What's setHow it layersResult
a dictmerges deeplykeys from both files remain
an empty dict {}merges deeplychanges nothing
a listgets replaced entirelythe top file replaces the bottom one
nullremoves the keythe template sees the value as absent

What actually applied

helm get values shows the final set of values for a release:

helm get values web -n demo        # overrides only
helm get values web -n demo -a     # the full set, including chart defaults

The -a (--all) flag matters when you're troubleshooting an incident. It shows exactly which value the app is actually running with, and saves you from guessing across several files. Before installing, you check the same thing with a render, no cluster needed:

helm template web ./myapp -f values/common.yaml -f values/prod.yaml

A typo in a key name passes silently: replicasCount: 3 just does nothing. If a values.schema.json sits next to the chart, Helm validates values against it on install, upgrade, lint and template, but an extra key is rejected only if the schema forbids it. You can't rely on the schema. A typo gets caught by comparing helm get values -a with what you meant to set.

Why a file, if there's --set

--set is good for one or two quick edits on the fly. The rest of the time, a file wins: it lives in git next to the code, people read it in full, and you can see its history. The command doesn't turn into a long tail of flags that nobody can reproduce later.

Common mistakes

A number instead of a string. YAML reads 1.25 without quotes as a floating-point number. Sometimes that's invisible: tag: 1.25 gives the image nginx:1.25. But tag: 1.10 turns into nginx:1.1, because the number has no trailing zero, and 20241001 after quote in the template becomes "2.0241001e+07". Write tags, versions and anything number-like in quotes.

Wrong nesting level. Your file has to repeat the structure of the chart's values.yaml. A value placed one level too high or too low won't raise an error, it just goes unused.

An empty dict instead of null. resources: {} in the top file doesn't cancel resources from the file below, the keys below still reach the template.

Values for a subchart live under its name: redis.auth.password, not auth.password. This is a common cause of "the setting isn't taking effect" when working with umbrella charts.

Homework

Practice the theory on your own cluster: 1 step with a check. Set up the cluster with the Kubernetes locally track, the tasks live in the k8s-practice repository.

./run.sh start k8s-helm-values-file     # set up the starting state
./run.sh verify k8s-helm-values-file    # check the current step

In short

What you'll need for the tasks

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson k8s-packaging-08-helm-values