Inside a Helm named template invoked as `include "app.labels" .Values.worker`, what does `$` refer to?
answer
- It is a variable, not a global
- Set once per template execution
- A helper call is a new execution
- The argument becomes both dot and root
- Look at the call site, not the helper
basics
~20 s$ is the root of the current template execution, and every include or template call starts a new one. Inside that helper $ is .Values.worker, the argument it was handed — so neither dot nor $ can reach .Values or .Release there.
solid answer
~50 s`$` is not a global. It is a variable initialised to whatever value the running template was invoked with, and a named-template call is a fresh invocation, so the helper's `$` is the argument — here `.Values.worker`. In a chart's own template file that argument is the chart context, which is why `$.Release.Name` rescues you from inside a `range` or `with` in the same file: the block did not start a new execution, so `$` still holds the chart root. Cross the boundary into an included helper with a sub-value and both `.` and `$` become that sub-value. A helper written to read `.Values.image.tag` then fails from its own line with a nil pointer complaint about `.Values` on a value that has no such key, wrapped by Helm as an error calling include. The fix is on the calling side: hand the helper what it needs.
go deeper
Recall that a helper only sees the one value it was given, and that $ inside it is that same value. Do not assume .Values is available everywhere just because it is available in the file you are editing.
Explain the boundary precisely: $ is initialised per template execution, range and with do not start one, and include does. Be able to read the two-layer error and say which line actually failed.
Demonstrate the diagnosis path — from a nil pointer inside _helpers.tpl back to the one call site passing a narrower value — and explain why editing the helper to be defensive usually hides the bug instead of fixing it.
Set the convention for a shared chart library: whether helpers take the root, take an explicit composite argument, or are documented per helper, and how you keep that consistent so nobody has to guess what a partial can see.
### Dot and dollar are both variables, and both are scoped A chart template is executed against a context object — the one holding `.Values`, `.Release`, `.Chart`, `.Capabilities`, `.Files`, `.Template` and `.Subcharts`. Two names refer to values during that execution. Dot is the current value, which blocks may rebind. `$` is a variable that is set once, at the start of the execution, to the value the execution was handed. That second sentence is the whole answer, and the word doing the work is *execution*. `range` and `with` rebind dot but stay inside the same execution, so `$` keeps pointing at the chart context and `{{ $.Release.Namespace }}` works from inside a loop. A named template is different: calling it starts a new execution with its own dot and its own `$`, both initialised to the single argument you passed. ### The failure this produces Helm charts hit this constantly because helpers live in `_helpers.tpl` and get called from all over the chart. Suppose a chart for an invoice-rendering worker defines: ```yaml {{- define "app.workerLabels" -}} app.kubernetes.io/component: {{ .name }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end -}} ``` and a template calls it with a sub-value: ```yaml labels: {{- include "app.workerLabels" .Values.worker | nindent 4 }} ``` The first line works: dot is the worker map and it has a `name` key. The second fails, because the worker map has no `Release` key. Reaching for `$.Release.Name` instead does not help, because `$` in that helper is also the worker map. The error comes from the helper's own file and line and complains about a nil pointer evaluating `Release` on an interface value, and because Helm implements `include` as a template function, the whole thing is reported as an error calling include from the caller's line. Reading only the outer half of that message sends people hunting for a typo in the `define` name, which is almost never the cause. The same thing happens without an explicit sub-value if the call site is inside a rebinding block: ```yaml {{- range .Values.queues }} {{- include "app.workerLabels" . | nindent 4 }} {{- end }} ``` Here dot at the call site is one queue element, so that element becomes the helper's dot and `$` alike. The block did not break `$` for the file it is written in — it broke it for the helper, by deciding what got passed. ### Why the scaffold usually works Most generated charts call helpers as `{{ include "app.labels" . }}` from a top-level template, where dot is still the chart context. The argument is the root, so inside the helper both `.` and `$` are the root, and `.Values` and `.Release` resolve normally. That is why the pattern feels like a global until the first time somebody passes something narrower. ### Reading the symptom When a helper cannot see chart-level objects, work backwards from the helper to its callers rather than editing the helper first: 1. Note which identifier failed. A complaint about `Values`, `Release`, `Chart` or `Capabilities` inside a file under `templates/` that starts with an underscore almost always means the helper was invoked with a narrower argument. 2. Find the call sites of that `define` name. There may be several, and only one of them may be passing a sub-value or sitting inside a `range` or `with`. 3. Check what dot is at each call site, not what it is at the top of the file. The repair belongs on the calling side: give the helper the context it needs to do its job, either by passing the root or by passing a composite value that carries both the root and the item. Which of those a chart should standardise on is a question about designing helpers, but the diagnosis above is what turns a confusing error into a one-line change. ### The mental model worth keeping Treat `$` as a parameter, not a global. It answers the question *what was this template called with*, and every template call re-answers it. Dot answers *what is the current value here*, and every `range` and `with` re-answers that. Once both questions are asked per invocation rather than per chart, the nil pointer messages stop being mysterious.
- Inside a `range` in the same template file, does `$` still reach `.Values`?Yes. `range` and `with` rebind dot but stay within one template execution, and `$` was initialised when that execution started, so it still holds whatever the file was executed with — the chart context. That is precisely why `$.Values` and `$.Release.Name` are the standard escape from inside a loop, and why the same trick fails once the loop body calls a helper with a narrower argument.
- What does the error look like when a helper reaches for `.Values` under a sub-scope?Two layers. The inner layer is a template error naming the helper's file and line and complaining about a nil pointer evaluating `Values` on the value that was passed. The outer layer is Helm reporting an error calling include from the caller's line, because include is a function. Read the inner half first: it names the identifier that was not found.
- Why does the pattern seem to work in a chart generated by `helm create`?Because those helpers are invoked from top-level templates where dot is still the chart context, so the argument is the root and the helper's dot and `$` are both the root. Nothing narrows the scope, so `.Values` and `.Release` resolve inside the helper. The illusion of a global only breaks the first time someone passes a sub-value or calls the helper from inside a rebinding block.
$ behaves like a function parameter, not a global variable: every include call is a new function call, so the callee's $ is whatever the caller handed over — never the chart's root by default.
saying these in an interview costs you the question
- Calls `$` a global that always reaches `.Values`
- Says `$` is always the chart's root context
- Blames a typo in the `define` name
- Thinks a helper inherits the caller's dot automatically
- Expects Helm to fall back to the root on a failed lookup
- Believes `range` is what broke `$` for the helper