skip to content

In a Helm chart's values.yaml, when do you nest keys and when do you keep them flat?

level: middleimportance: should knowfreq 55%

answer

  1. Why partial override works at all
  2. Group by the object being configured
  3. What happens when your chart is a dependency
  4. Every level is characters someone types
  5. Dots inside key names are separators

basics

~20 s

Nest to group the knobs of one thing, so a caller overrides a single leaf without restating its siblings - maps merge key by key. Keep it shallow: as a subchart, every path gains your chart's name as a prefix.

solid answer

~50 s

Group values around the object they configure - `image.repository`, `image.tag`, `image.pullPolicy` - because Helm merges maps key by key, so a caller setting `image.tag` keeps your repository and pull policy defaults. That is the whole argument for nesting: it makes partial override cheap. The argument against going deeper is length and portability. Every level shows up in what people type and in what they read, and the moment your chart is consumed as a dependency the parent addresses all of it under your chart name or alias, so a three-level path becomes four. Two or three levels covers nearly everything real. Prefer conventional names people can guess (`nameOverride`, `fullnameOverride`, `podAnnotations`, `nodeSelector`) over house style, avoid putting a dot inside a key name because path syntax reads dots as separators, and never split one concept across a flat key and a nested map at the same time.

code

yaml · 15 lines
yaml
replicaCount: 2
fullnameOverride: ""
image:
  repository: registry.example.com/platform/reranker
  tag: "1.9.3"
  pullPolicy: IfNotPresent
service:
  type: ClusterIP
  port: 8080
serviceAccount:
  create: true
  name: ""
reranker:
  candidatePoolSize: 384
  modelRevision: r7

go deeper

for a junior

Know the shape of a conventional values file - image, service, resources, nodeSelector - and be able to say why image.tag is nested under image rather than spelled imageTag.

for a middle

Explain the merge mechanics that make nesting useful: maps coalesce key by key so partial overrides keep sibling defaults, while lists are replaced whole. Then explain the cost - depth, nil-safety and the prefix a subchart install adds.

for a senior

Show that you design for the consumer you cannot see: conventional names, two or three levels, mirroring upstream field names for pass-through blocks, and consistency across every chart your team publishes.

for a principal

Own the house style across many charts. Be ready to say how you keep naming consistent, what you do when an inherited chart uses a different vocabulary, and why the cost of an inconsistent key is paid by every installer forever.

### What nesting actually buys you Helm coalesces values as maps. When a caller supplies `image.tag`, Helm walks into the `image` map and replaces that one leaf; the sibling keys from `values.yaml` survive. That is the practical reason to group related knobs under a parent: a consumer can change one thing without restating the rest, and your defaults keep working for everything they did not mention. Flatten the same three settings into `imageRepository`, `imageTag` and `imagePullPolicy` and you lose nothing functionally, but you also gain nothing, and the file stops telling a reader that those three belong together. The grouping that works is by the thing being configured, not by an abstract concern. `service.type` and `service.port`; `ingress.enabled`, `ingress.className` and `ingress.hosts`; `serviceAccount.create` and `serviceAccount.name`. A reader scanning the file can map each block onto a rendered object. Grouping by concern instead - a `networking:` map holding a Service port and an Ingress host and a NetworkPolicy toggle - reads well in the file and badly at the command line, because nobody guesses which concern you filed their setting under. ### What nesting costs Each level costs characters in every override anyone writes, and it costs a level of nil-safety in your own templates: `.Values.a.b.c.d` is three intermediate maps that must all exist. It also costs portability. Charts get reused as dependencies, and when your chart is pulled into an umbrella - say a monitoring-stack chart that depends on three subcharts - the parent does not address your keys at the top level. They live under a key named for your chart, or for the alias the parent gave it. Your `exporter.image.tag` becomes `reranker-exporter.exporter.image.tag` in the parent's values file. Two levels in your chart is four in theirs; four is six, and by then people are copying paths rather than writing them. So the rule of thumb: nest where partial override is genuinely wanted, stop at two or three levels, and resist a level that exists purely for tidiness. A single scalar that configures nothing else - `replicaCount`, `fullnameOverride`, `priorityClassName` - stays flat at the top. It has no siblings to group with, and burying it under a `deployment:` map buys nothing and lengthens every override. ### Names that survive contact with strangers Because the path is the interface, the names matter as much as the shape. - **Follow the conventions.** `helm create` establishes a vocabulary the whole ecosystem shares: `replicaCount`, `image`, `imagePullSecrets`, `nameOverride`, `fullnameOverride`, `serviceAccount`, `podAnnotations`, `podSecurityContext`, `securityContext`, `resources`, `nodeSelector`, `tolerations`, `affinity`, `autoscaling`, `ingress`. A consumer who has installed ten charts can already write half your values file. Renaming any of these to house style buys you nothing and costs them a search. - **Mirror upstream field names when you pass values straight through.** If a key ends up verbatim inside a Kubernetes object, name it as that object names it, so people can read the platform's own documentation and paste the result in. - **Avoid dots inside key names.** `node.role: worker` is legal YAML, but path syntax reads the dot as a separator, so callers have to escape it to address the key. If you need arbitrary user-chosen strings - annotation keys, label keys - accept them as the *contents* of a map you range over, not as the shape of your API. - **Be consistent across your own charts.** Nine charts from the same team should spell the same idea the same way. One using `extraEnv` and another `env.extra` is a small tax paid on every install, forever. ### Where flat wins outright A top-level boolean that gates a whole feature reads best flat and short. A single map keyed by something the *user* names - environment variables, extra labels, arbitrary annotations - is not really nesting at all; it is a one-level map whose keys are data, and it should stay one level deep so people can paste into it. And a value that two unrelated templates both read is better as a flat top-level key than duplicated under two parents, because the duplicate pair will diverge the first time someone overrides only one of them.

  • A caller passes a values file that sets only image.tag. Why do the chart's repository and pullPolicy survive?
    Because Helm merges maps key by key rather than replacing them. The supplied `image` map is coalesced with the chart's, so only `tag` changes. Lists behave differently - a list in a caller's file replaces the chart's list entirely rather than merging into it, which is why the map-versus-list choice matters when you shape a key.
  • Your chart is about to be published for use inside umbrella charts. Does that change how you name keys?
    It changes how long they can afford to be and how generic they can afford to be. Every path gains a prefix - your chart's name or the alias the parent chose - so two levels becomes four in the parent's file. It also argues for names that still read correctly with that prefix attached, and against generic top-level names that mean nothing once they sit under someone else's chart.
  • How do you let users supply arbitrary keys, like annotation names, without wrecking the path model?
    Accept them as the contents of a one-level map that a template ranges over or renders with `toYaml`, rather than as part of the chart's own key shape. The user's strings are then data, not API. They may still need escaping if someone addresses one directly on the command line, but a values file with the map pasted in works without any escaping at all.

saying these in an interview costs you the question

  • Nests four or five levels deep because the file looks tidier
  • Thinks a caller's partial map replaces the chart's whole map
  • Invents house names instead of the conventional ones
  • Puts a dot inside a key name and expects direct addressing to work
  • Groups by abstract concern, so nobody can guess the path
  • Forgets that a subchart's keys gain the chart name as a prefix

context