Skip to content
Latchkey

Helm "lookup" Returns Empty During template/Dry-Run in CI

Helm’s lookup function queries the live cluster, but it returns an empty value during helm template and helm install --dry-run (no cluster connection in render-only mode). Templates that assume lookup always returns data break in CI rendering.

What this error means

A chart that reads an existing Secret/ConfigMap with lookup renders empty fields (or a nil-pointer error) under helm template/--dry-run, even though the object exists, because lookup is not executed against the cluster in those modes.

helm output
Error: template: chart/templates/secret.yaml:8:14: executing "..." at <.data.password>:
nil pointer evaluating interface {}.password
# lookup "v1" "Secret" ... returned an empty dict during helm template

Diagnose it: render the chart before you install it

Most Helm failures are visible in the rendered manifests. Template them locally with the same values CI uses and you will usually see the problem without touching the cluster.

Terminal
# what will actually be applied
helm template <release> <chart> -f values.ci.yaml | head -60

# validate against the live cluster schema without installing
helm install <release> <chart> -f values.ci.yaml --dry-run --debug

# what state is the release actually in?
helm history <release>
helm status <release>

Common causes

lookup is a no-op in render-only modes

helm template and helm install --dry-run (client) do not talk to the cluster, so lookup returns an empty map/dict. Code that dereferences the result without guarding then fails.

No default for the missing value

Even during a real install, the looked-up object may not exist yet (first install). Without a fallback, the template errors or renders an empty value.

How to fix it

Guard lookup with a default and existence check

Treat an empty lookup as "not found" and fall back, so render-only modes and first installs both work.

templates/secret.yaml
{{- $existing := (lookup "v1" "Secret" .Release.Namespace "api-secret") | default dict }}
password: {{ (get (default dict $existing.data) "password") | default (randAlphaNum 24 | b64enc) }}

Use server-side dry-run when you need real lookups

  1. Run helm template only for static rendering - expect lookup to be empty there.
  2. Use helm install --dry-run=server to exercise lookups against the cluster.
  3. Never rely on lookup for values that must exist at first install; provide defaults.

How to prevent it

  • Always | default dict a lookup result and guard before dereferencing.
  • Reserve lookup for genuinely cluster-dependent values, with fallbacks.
  • Use --dry-run=server when CI validation needs real cluster state.

Frequently asked questions

What causes Helm "lookup" returns empty during template/Dry-Run in CI?
There are 2 common causes: lookup is a no-op in render-only modes and no default for the missing value. helm template and helm install --dry-run (client) do not talk to the cluster, so lookup returns an empty map/dict.
How do I fix Helm "lookup" returns empty during template/Dry-Run in CI?
There are 2 fixes depending on which cause you have: guard lookup with a default and existence check and use server-side dry-run when you need real lookups. Work through them in order, since the first is the most common.
What does Helm "lookup" returns empty during template/Dry-Run in CI actually mean?
A chart that reads an existing Secret/ConfigMap with lookup renders empty fields (or a nil-pointer error) under helm template/--dry-run, even though the object exists, because lookup is not executed against the cluster in those modes.
How do I stop Helm "lookup" returns empty during template/Dry-Run in CI happening again?
Always | default dict a lookup result and guard before dereferencing. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card