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.
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 templateCommon 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.
{{- $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
- Run
helm templateonly for static rendering - expectlookupto be empty there. - Use
helm install --dry-run=serverto exercise lookups against the cluster. - Never rely on
lookupfor values that must exist at first install; provide defaults.
How to prevent it
- Always
| default dictalookupresult and guard before dereferencing. - Reserve
lookupfor genuinely cluster-dependent values, with fallbacks. - Use
--dry-run=serverwhen CI validation needs real cluster state.