Whose credentials does Helm's lookup function use, and when does it return nothing?
answer
- the client does the reading
- no in-cluster component, no separate identity
- your kubeconfig is the chart's reach
- offline renders see an empty value
- the manifest stops being a pure function
basics
~20 slookup reads the live cluster from the Helm client, using the kubeconfig of whoever ran the command - there is no in-cluster component and no separate identity. It returns an empty value during helm template and a client-side dry run.
solid answer
~50 s`lookup` performs a real read against the API server at render time, from the machine running `helm`, with that caller's kubeconfig and RBAC. Two consequences follow. First, **rendering is not pure**: the same chart and the same values produce different manifests for different callers and at different times, so a CI render and the actual install can disagree. Second, **the reader's privilege is what applies** - a cluster-admin operator installing an unfamiliar chart lends it their whole read surface, and whatever the template prints lands in the rendered manifest. `helm template` and a client-side dry run make no API call at all, so `lookup` yields an empty value there and any `if` branch built on it takes the empty path; a server-side dry run does reach the cluster. That is why a chart preserving an existing password appears to regenerate it whenever you render offline.
code
yaml · 9 lines{{- $name := printf "%s-redis" .Release.Name }}
{{- $existing := lookup "v1" "Secret" .Release.Namespace $name }}
apiVersion: v1
kind: Secret
metadata:
name: {{ $name }}
type: Opaque
data:
password: {{ if $existing }}{{ index $existing.data "password" }}{{ else }}{{ randAlphaNum 24 | b64enc }}{{ end }}go deeper
Know that lookup reads a live object during rendering and that it comes back empty from helm template, so offline output can differ from what actually gets installed. Recognise the function when you see it in a chart.
Explain the mechanics: rendering is client-side, the read uses the caller's kubeconfig, an empty result is falsy so charts branch on it, and everything renders before anything is applied - which is why a chart cannot look up its own new objects.
An interviewer expects you to connect it to blast radius: running an unfamiliar chart with an admin credential lends that chart your whole read surface. Be ready to diagnose renders that differ between callers, or between CI and install.
Own whether charts in your delivery path may call lookup at all. It couples rendered output to live cluster state and to the deploy identity's privileges, which affects reviewability, reproducibility and the credential you are willing to hand a pipeline.
## What lookup is `lookup` is a Helm-specific template function that reads live objects from the cluster while a chart is rendering. It takes an API version, a kind, a namespace and a name, and returns a map of that object or an empty value if it is not there. Passing an empty name returns a list object whose `items` you can range over; passing an empty namespace searches cluster-wide for cluster-scoped kinds. The canonical use is preserving generated state. A Redis chart that generates a password on first install would generate a *new* one on every upgrade, because templates are re-rendered from scratch each time. The usual fix is to look the existing Secret up first and reuse its value if it is there. ## Who is doing the reading This is the part interviewers are actually probing. Since Helm 3 there is no in-cluster server component. `helm` is a client: it renders templates locally, then talks to the API server using your kubeconfig - the same current context `kubectl` would use, honouring `--kube-context` and the namespace resolution that `-n` and `HELM_NAMESPACE` drive. `lookup` is just another request on that connection. So: - there is no Helm service account, and no way to give `lookup` a different identity from the one applying the release; - the read is bounded by whatever RBAC the caller has, not by anything the chart declares; - the chart cannot see anything you cannot see - but it *can* see everything you can. That asymmetry is the risk. A platform engineer with cluster-admin rights who runs `helm upgrade` on a chart authored elsewhere is lending that chart their whole read surface for the duration of the render. A template can look up a Secret in another namespace and print it into a ConfigMap the chart renders, and the render will succeed, because the operator was allowed to read it. Nothing about the chart declares an intent to do so - the only way to find out is to read the templates or diff the render. ## When lookup returns nothing `lookup` is inert whenever Helm renders without talking to a cluster. `helm template` renders locally and returns an empty value from every `lookup`. A client-side dry run does the same. A server-side dry run does reach the API server, so `lookup` behaves as it does during a real install. This matters more than it sounds, because the empty value is falsy and charts branch on it. The password example inverts: rendered offline, the `if` sees nothing, takes the generate branch, and shows a brand-new password. Engineers reading that output conclude the chart rotates the password on every upgrade, when in reality the install path finds the existing Secret and keeps it. The reverse bites too - a CI job that renders a chart and diffs the output against the last release will report churn on exactly the fields that come from `lookup`, forever. So `lookup` breaks the property that makes a chart reviewable: that the rendered manifest is a function of the chart plus the values. With `lookup` in play the manifest is a function of the chart, the values, the cluster's current contents, and who is looking. ## The failure modes worth naming **Ordering and first install.** On a first install nothing exists yet, so every `lookup` in the chart returns empty. A chart that depends on an object it also creates cannot see it during the same render - rendering happens entirely before anything is applied. **Hooks do not trigger a re-render.** Manifests are rendered once per operation, so a `lookup` does not run again after a hook creates the object it was looking for. **A failed read is not the same as an empty one.** An object that genuinely does not exist yields the empty value. A read that fails for another reason surfaces as a rendering error rather than quietly becoming empty - so a chart that renders for one engineer and fails for another usually means the second one lacks the read, not that the object is missing. **Values that came from a lookup are not in Git.** Whatever the template printed from the live object becomes part of the rendered manifest for that revision, which is what Helm applies. The chart source alone no longer tells you what was deployed. ## How to handle it As a chart *consumer*: before installing a chart you did not write, render it and read the output, and grep the templates for `lookup` - that grep is the difference between "this chart deploys these objects" and "this chart reads my cluster". Run the install with the narrowest identity that works rather than a personal admin credential, so the render's reach matches the deploy's reach. As a chart *author*: use `lookup` sparingly and only for state that genuinely cannot be supplied as a value. Guard every use so the chart still renders sensibly when it returns empty, and document that `helm template` output will differ from the installed manifest for those fields. Where the goal is only to avoid regenerating a credential, an external secret operator or a pre-created Secret referenced by name is a more honest design than a chart that reads the cluster to decide what to write. As a *platform owner*: decide whether charts running in your pipelines may call `lookup` at all. Because it executes with the deploy identity, a chart's read surface is your pipeline's read surface, and that is a property of the chart you installed rather than of Kubernetes.
- Your CI job diffs helm template output against the previous run and always reports churn on one field. What would you suspect?A `lookup` feeding that field. `helm template` makes no API call, so the lookup returns empty and the chart takes its fallback branch - often a freshly generated value - on every render. The diff is comparing two offline renders, neither of which matches the installed manifest. Either render with a server-side dry run against the target cluster, or exclude lookup-derived fields from the comparison.
- Can you give lookup a narrower identity than the one applying the release?No. Helm is a client with one connection to the API server, established from the caller's kubeconfig, and `lookup` uses it like every other call. The read surface and the write surface are the same identity. If you need the render to see less, the lever is the credential the pipeline runs as, not a chart-level setting.
- Why does a lookup in a chart never see an object the same chart creates?Because all templates are rendered before anything is applied. The render produces one complete manifest, and only then does Helm send it to the API server, so on a first install every lookup for the chart's own objects returns empty. A hook that creates an object later does not trigger a re-render either.
saying these in an interview costs you the question
- Thinks lookup runs server-side inside the cluster
- Says Helm has its own service account for reads
- Expects lookup to work during helm template
- Believes a chart can only read objects it created
- Treats rendered output as chart plus values alone
- Assumes a failed read silently returns empty