skip to content

How do you find every container image a third-party Helm chart will pull, and pin it?

level: seniorimportance: should knowfreq 46%

answer

  1. No Helm command answers this — the render does
  2. Values under-report; subcharts and hooks carry their own
  3. An unset tag may follow a Chart.yaml field
  4. A colon in the template blocks one addressing form
  5. Rewrite the render when values cannot reach it

basics

~20 s

Enumerate from the render, not the values file: helm template with your real values shows every image line, including initContainers and hook Jobs. Pinning works only where a template exposes that reference as a value.

solid answer

~50 s

There is no `helm images` command, and `helm show values` under-reports: subcharts, hook Jobs and initContainers frequently carry images the parent's value table never mentions, and some are hardcoded in a template with no value behind them. So enumerate from the render — `helm template <name> <chart> -f prod-values.yaml --include-crds | grep image:` — and treat that list as the chart's real dependency set. Then check *how* each reference is built. The common scaffold is `{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}`, which means an unset tag follows the chart's `appVersion`: bumping the chart moves the running image even when your values did not change. Where a template concatenates only a tag, a digest cannot be supplied through values at all; the options are a post-renderer that rewrites the rendered YAML, or vendoring and editing the chart. Re-render and diff the image list on every chart bump.

code

bash · 2 lines
bash
helm template checkout ./checkout-platform -f prod-values.yaml --include-crds \
  | grep -E '^[[:space:]]*-?[[:space:]]*image:[[:space:]]' | sort -u

go deeper

for a junior

Know that the images a chart pulls are visible in the rendered manifests, and that reading values.yaml alone can miss some of them.

for a middle

Explain how a reference is built — repository plus tag from values, a tag defaulting to Chart.yaml's appVersion — and why a subchart's image values sit under the subchart's key.

for a senior

Show the working method: enumerate from the render, verify each override actually changed the output, and know the escape hatches when a reference is hardcoded rather than parameterised.

for a principal

Own the tradeoff between staying on an upstream chart with a render-rewriting step and vendoring it outright, and decide who carries the ongoing merge cost across an estate of vendor charts.

### The list you need does not exist as a command Helm has no command that answers "what images will this chart pull". The chart's `values.yaml` is a partial answer at best, because an image reference reaches the cluster through a *template*, and a template can build that string from values, from `Chart.yaml` fields, from a helper in `_helpers.tpl`, or from nothing at all. The only complete source is the render. ``` helm template checkout ./checkout-platform -f prod-values.yaml --include-crds \ | grep -E '^[[:space:]]*-?[[:space:]]*image:[[:space:]]' | sort -u ``` Run that against an umbrella chart bundling an order-checkout API and a worker subchart and the list is routinely longer than expected: the API and worker images, a migration image inside a `pre-upgrade` hook Job, a chown initContainer, a metrics sidecar, and whatever the vendored subchart under `charts/` brings with it. Every one of those runs in your cluster with your pull secrets. ### Where each reference comes from Having the list, read how each was produced, because that decides what you can control. **Values-driven, tag only.** The `helm create` scaffold pattern is `image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"`. Two consequences. First, leaving `image.tag` unset couples the running image to the chart's `appVersion`, so a chart upgrade changes the image even if your values file is untouched — the chart version and the app version are different fields, and this is where their difference bites. Second, the template concatenates a colon: supplying a digest through `image.tag` produces `repo:sha256:...`, which is not a valid reference. Some charts add a separate `image.digest` value that emits `repo@sha256:...` when set; many do not. **Subchart-scoped.** A subchart's image values live under the subchart's key in the parent's values (`worker:` `image:` `tag:`), and the parent may or may not expose them through its own value table. `helm show values` on the parent shows whatever the parent's `values.yaml` file contains, which for a vendored subchart may be nothing. **Hardcoded.** A hook Job or initContainer image written literally into a template has no value behind it. No `--set` reaches it. ### Pinning, in order of preference 1. **Values, when the chart supports it.** Set the repository to your mirror and the tag or digest value explicitly for every image the render showed — including the subchart-scoped ones. Then re-render and confirm the output actually changed; a value you set that never appears in the render was the wrong key. 2. **Rewrite the render.** A post-renderer receives Helm's rendered manifests on stdin and returns modified YAML, so it can replace image references the chart never parameterised. In Helm 4 `--post-renderer` takes the **name of an installed `postrenderer/v1` plugin**, not a path to an executable, which is a real break from Helm 3 automation that pointed it at a script. This keeps you on the upstream chart at the cost of a transformation nobody sees in the chart source. 3. **Vendor and edit.** Copy the chart into your own repository at a known version and change the templates. Total control, and you now own merging every upstream release. ### Why this is a review step and not a one-off The image set is a property of chart version *and* values, so it changes underneath you in two ways. A values change can add an image — enabling persistence pulls in an initContainer, enabling metrics adds a sidecar. A chart bump can add, remove or move images silently, especially where a tag defaults to `appVersion`. The maintainable habit is to render both versions with the same production values, diff, and read the image lines of the diff first. On an upgrade of the checkout umbrella chart from 4.7.2 to 5.0.0, that diff is the difference between "a minor version bump" and "a new vendored subchart with a migration hook pulling an image from a registry we do not mirror". ### The boundary of what this buys you Enumerating and pinning tells you exactly which bytes will run and stops them changing under you between a review and a deploy. It does not tell you the images are trustworthy, and it does not stop a cluster from running something else if the reference is resolvable to different content later. Those are separate controls; this one is about making the chart's behaviour deterministic and reviewable in the first place.

  • You set image.tag in your values file but the rendered pod spec is unchanged. What happened?
    You set a key nothing reads. The most common cause is a subchart: its image values live under the subchart's name in the parent's values, so `image.tag` at the top level never reaches it. The other cause is a hardcoded reference in the template, which no value can override. Re-rendering after every values change is what catches both.
  • A chart's template joins repository and tag with a colon. How do you deploy that image by digest?
    Not through that value — `repo:sha256:...` is not a valid reference. Either the chart offers a separate digest value that emits `repo@sha256:...`, or you rewrite the rendered manifests with a post-renderer, or you vendor the chart and edit the template. In Helm 4 `--post-renderer` names an installed postrenderer/v1 plugin rather than pointing at an executable.
  • Why re-run the image enumeration when only the chart version changed?
    Because the image set is a function of chart version as well as values. A bump can add a vendored subchart, add a hook Job with its own image, or move a tag that defaulted to `appVersion`. Rendering the old and new versions with the same production values and diffing the image lines is the cheapest way to see it.

saying these in an interview costs you the question

  • Trusting helm show values to list every image
  • Assuming the README's image table is complete
  • Forgetting hook Jobs and initContainers carry images
  • Setting a parent value expecting it to reach a subchart
  • Putting a digest into a tag value joined by a colon
  • Pinning once and never re-checking after a chart bump

context