Emacs
Helm
Global Scope
Code Navigation
Programming Tools

Helm range without leaving global scope

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Helm templates, range changes the meaning of the dot value. Inside the loop, . becomes the current item, which is why many templates suddenly lose access to .Values, .Chart, or .Release. The fix is to keep a reference to the root context and use it explicitly.

Why Scope Changes Inside range

Helm templates use Go's text templating rules. The current context is stored in .. When you write a range, Helm rebinds . for each element in the collection.

That means this template is fragile:

yaml
1{{- range .Values.ports }}
2- containerPort: {{ . }}
3  app: {{ .Values.appName }}
4{{- end }}

Inside the loop, . is now a port value like 8080, not the original top-level chart context. So .Values.appName no longer exists there.

Use $ for the Root Context

Helm exposes the original root as $. You can access global values from anywhere in the template by using $.Values, $.Release, or $.Chart.

yaml
1{{- range .Values.ports }}
2- containerPort: {{ . }}
3  app: {{ $.Values.appName }}
4{{- end }}

That is the most direct answer to "how do I range without leaving global scope?" You do not stop scope from changing. You keep a pointer to the global scope and reference it when needed.

Save the Root in a Named Variable

For more complex templates, many chart authors save the root context into a variable for readability.

yaml
1{{- $root := . }}
2ports:
3{{- range $root.Values.ports }}
4  - name: {{ $root.Values.appName }}-{{ .name }}
5    port: {{ .port }}
6{{- end }}

This becomes especially useful when nested range and with blocks appear in the same file.

Example With Maps and Nested Objects

Suppose values.yaml contains:

yaml
1appName: demo
2ports:
3  - name: http
4    port: 80
5  - name: https
6    port: 443

A working template could be:

yaml
1{{- $root := . }}
2apiVersion: v1
3kind: Service
4metadata:
5  name: {{ $root.Values.appName }}
6spec:
7  selector:
8    app: {{ $root.Values.appName }}
9  ports:
10{{- range $root.Values.ports }}
11    - name: {{ .name }}
12      port: {{ .port }}
13      targetPort: {{ .port }}
14{{- end }}

Inside the loop, .name and .port come from the current port item, while $root.Values.appName still reads the global chart value.

with Has the Same Kind of Scope Change

This problem is not unique to range. with also changes the meaning of ..

yaml
1{{- with .Values.image }}
2image: {{ .repository }}:{{ .tag }}
3release: {{ $.Release.Name }}
4{{- end }}

The same rule applies: use . for the local narrowed object, and use $ or a saved root variable when you need the global context.

When to Use Local Variables

If a value is used repeatedly, store it once.

yaml
1{{- $root := . }}
2{{- $appName := .Values.appName }}
3{{- range .Values.ports }}
4- name: {{ $appName }}-{{ .name }}
5  release: {{ $root.Release.Name }}
6{{- end }}

This avoids long expressions and makes the template easier to review.

Common Pitfalls

A common mistake is assuming range iterates without changing .. In Helm, that assumption breaks many templates immediately.

Another mistake is mixing . and $ carelessly in nested loops. If the template is hard to read, save the root into a named variable and use clear local names.

Chart authors also sometimes try to solve the problem by copying values into each item. That is unnecessary if the only issue is scope. Root references already solve it cleanly.

Summary

  • In Helm, range rebinds . to the current loop item.
  • Use $ to access the original root context from inside the loop.
  • Saving . into a variable like $root improves readability in complex templates.
  • The same scoping rule applies to with blocks.
  • The goal is not to stop scope changes, but to reference the correct scope explicitly.

Course illustration
Course illustration

All Rights Reserved.