Two charts in one Helm release both define a template named common.labels - what happens?
answer
- Names are not scoped to a chart
- No error is raised at all
- Load order decides the winner
- Prefix exported names with the chart name
basics
~20 sNamed templates share one global namespace across a Helm chart and everything under charts/. Nothing errors: one definition wins by load order and every caller in the release gets it. That is why library helpers are prefixed with the chart name.
solid answer
~40 sHelm does not scope `define` names per chart. The parent, its subcharts and any library chart it depends on all contribute to a single template namespace for the render, so two `define "common.labels"` blocks are a collision. Helm raises no error - the definition loaded last wins, and load order is not something to design around. The consequences are worse than a wrong label: a subchart that calls `common.labels` may silently get a definition it has never seen, so one chart's helper can change another chart's output. The convention that prevents it is namespacing every exported name with the chart's own name (`platform-common.labels`), which is exactly why library charts are written that way and why a consumer should never define a bare, generic helper name.
code
yaml · 4 lines{{- define "platform-common.labels" -}}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}go deeper
Remember that a named template's name is global to the whole render, not private to the file or chart it sits in. Prefixing helper names with the chart name is the habit to copy.
Explain that the parent, subcharts and library charts all load into one template set, that a duplicate name is resolved by load order with no error, and that callers in other charts are affected too.
Show how you would find this in a real fleet: render and diff rather than reason about it, and treat an exported helper name as a public interface that cannot be renamed casually.
Frame naming as governance - a shared library's exported names are an API surface across many teams, so a naming convention and a deprecation path matter more than any individual helper's body.
### One namespace, not one per chart When Helm renders a release, it loads the templates of the top-level chart and of every chart beneath it under `charts/` - subcharts, and any library chart pulled in as a dependency - into a single template set. Files are scoped by path, but `define` names are not scoped by anything. A block declared as `define "common.labels"` in a library chart and a block declared with the same name in the application chart are two definitions of one name. Helm's documentation is explicit that template names are global and that if two share a name, the one loaded last is the one used. There is no duplicate-definition error, no warning during a render, and no report from linting a single chart in isolation, because the collision only exists once the charts are combined into a tree. ### Why it is worse than it sounds If the collision only affected the chart that authored the losing definition, it would be a self-inflicted wound. It does not. Every call site in the whole release resolves through the same namespace. So a parent chart that defines `common.labels` for its own convenience can silently change the labels a subchart emits, because the subchart's `include "common.labels"` now finds the parent's block. That is a real mechanism - people have deliberately used it to monkey-patch a vendored chart's helper - but as an accident it produces a change to an object nobody edited, in a chart nobody touched. Labels make this particularly nasty because some of them end up in a selector, and a selector on a workload is immutable after creation. A silently different label block can therefore turn into an upgrade that fails on an immutable field rather than a cosmetic diff. ### Detecting it There is no built-in check that says "this name is defined twice". The practical detections are all render-based: render the consumer chart with its dependencies vendored and compare the output against what the chart produced before the dependency was added, and grep the whole tree - the chart plus everything under `charts/` - for the `define` name when a helper behaves unexpectedly. On a 12-namespace fleet the symptom usually arrives as "two services that should be identical emit different labels", and the difference is which other charts happen to sit in each release. ### The convention that fixes it Namespace every exported name with the chart it comes from. A library chart named `platform-common` exports `platform-common.labels`, `platform-common.fullname`, `platform-common.image`. A consumer's private helper is named after the consumer: `billing-cron.env`. Then no two names can collide unless two charts share a name, which the dependency machinery already forbids within a tree. Two corollaries follow. First, a generic name is a defect in a library chart, not a convenience - `common.labels` is precisely the name most likely to already exist in someone's chart. Second, an exported name is part of the library's public interface: renaming one is a breaking change to every consumer that calls it, and it deserves the same major-version treatment as any other API break. ### The related trap The same global namespace explains a second surprise. Because names, not files, are global, splitting a library's helpers across `_labels.tpl` and `_names.tpl` buys you nothing in isolation - the files are organisational only. And because a helper's body is evaluated against whatever context the caller passed, a library helper cannot rely on its own chart's metadata; a shared helper that reads `.Chart.Name` renders the *consuming* chart's name. Neither of these is a bug to be fixed; both are consequences of a template set being flat, and knowing that flatness is what makes a chart tree predictable to reason about. ### Why Helm works this way It is worth understanding rather than resenting. Go's template engine keeps an associated set of templates addressed by name, and Helm loads a chart tree into one such set so that a parent can call anything its dependencies define without an import mechanism. That flatness is exactly what makes a library chart possible: there is no linking step, no export list, no namespacing built into the language - a dependency's definitions are simply there. The cost of having no import system is having no collision detection either, and the convention of prefixing names with the chart is the community's stand-in for both. So the rule to carry away is that a chart's exported helper names are a shared resource across the whole release, and treating them as private because they live in your file is the mistake.
- Can a parent chart deliberately override a helper defined by a chart it depends on?Yes, and people do - redefining the name in the parent replaces it for the whole render, which is a way to patch a vendored chart you cannot change. It is fragile: it depends on load order rather than an interface, and it silently changes every other caller of that name in the release.
- Does helm lint catch a duplicate define name across a chart and its dependencies?Do not count on it. The collision only exists once the tree is assembled, and the reliable signal is the rendered output, not a lint rule. Render the consumer chart with its dependencies vendored and diff the result when you add or bump a library.
saying these in an interview costs you the question
- Claims Helm errors on a duplicate template name
- Thinks define names are scoped per chart
- Assumes the parent's definition always wins
- Believes a subchart cannot be affected by the parent
- Names a library helper common.labels for readability