lookup, tpl and library charts

Helm · Expert · 12 min

Large chart estates and platform teams rely on these to stay DRY and correct.

Powerful functions, each with a catch:

  • lookup "v1" "Secret" .Release.Namespace "shop-db" reads a live object during rendering, e.g. to keep a generated password stable across upgrades instead of regenerating it. It returns an empty map under helm template and client-side dry runs (no cluster access), so templates must handle that case, and it makes rendering depend on cluster state, which GitOps tools often can't provide.
  • tpl .Values.annotationsTemplate . renders a value string as a template, so users can write {{ .Release.Name }} inside values. Handy for chart authors, but it's easy to make values unreadable.
  • fail "message" and required stop rendering with a clear error when values are inconsistent (ingress enabled with no hosts).
  • dict, list, merge, mergeOverwrite, deepCopy, toJson, fromYaml let helpers take structured arguments and build objects.

Library charts (type: library in Chart.yaml) contain only named templates and can't be installed. Application charts depend on one and call its helpers, e.g. {{ include "common.deployment" (dict "root" . "name" "api") }}. Platform teams use this to give dozens of services identical labels, probes and security contexts while each service chart stays tiny.

Keep charts boring: templating everything is the road to an unmaintainable chart. If you need real logic, consider generating manifests with a proper language or using an operator.

Example

{{/* Keep a generated password stable across upgrades */}}
{{- $existing := lookup "v1" "Secret" .Release.Namespace (printf "%s-db" .Release.Name) }}
{{- $pw := "" }}
{{- if $existing }}
{{-   $pw = index $existing.data "password" | b64dec }}
{{- else }}
{{-   $pw = randAlphaNum 24 }}
{{- end }}
apiVersion: v1
kind: Secret
metadata:
  name: {{ .Release.Name }}-db
  annotations:
    {{- tpl (toYaml .Values.extraAnnotations) . | nindent 4 }}
stringData:
  password: {{ $pw | quote }}
{{- if and .Values.ingress.enabled (not .Values.ingress.hosts) }}
{{-   fail "ingress.enabled requires at least one ingress.hosts entry" }}
{{- end }}

Remember

  • lookup reads live objects but is empty during helm template
  • tpl renders values as templates; fail/required give clear errors
  • Library charts share helpers across many service charts

Watch out for

  • Relying on lookup in a chart deployed by a GitOps tool that renders without cluster access, regenerating secrets each time.

Check yourself

  1. What does lookup return when you run helm template without a cluster?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: helm.sh/docs/chart_template_guide/functions_and_pipelines/