Authoring and Shipping Helm Charts
Library Charts — Useful Precisely Because They Cannot Be Installed
Summary in one line
A library chart is a chart that holds only define blocks and renders nothing on its own, and it is the place where standard labels and the shape of common workloads are nailed down in one spot.
Why this is needed
When you first create a chart, _helpers.tpl is always born in the same shape. It is the three that helm create puts in for you: fullname, labels, and selectorLabels. The trouble starts when there are ten charts. Those ten _helpers.tpl files began as copies, but as one team adds app.kubernetes.io/component and another adds a team label, they become different creatures.
Why this is a problem becomes clear if you look at the side that uses the labels. Monitoring rules pick targets by app.kubernetes.io/instance, network policies pick by app.kubernetes.io/name, and the cost dashboard groups by team. If one label line is missing in a single chart, only that workload quietly falls outside the rules. During an outage, a report comes up saying "only this Pod has no metrics," and the cause is a line that was dropped from a half-year-old copy.
This is where the requirement comes from that changing the standard labels must be doable in one go. For that, the rule must be in a single file, and that file must be something many charts can take as a dependency.
How it works
If you set type in Chart.yaml to library, Helm excludes this chart from installation. If you actually try to install it, it refuses like this.
Error: INSTALLATION FAILED: library charts are not installable
helm template stops for the same reason. Instead, this chart can go into another chart's dependencies, and the moment it does, every define in it joins the parent chart's template name space. The parent takes a whole Deployment with the single line include "platform-lib.deployment" ..
The key point, and also the trap, is that template names are a single name space across the whole chart. If a name defined by a subchart and a name defined by the parent are the same, the one read later wins, and the parent is read later. If they overlap by accident, a different template is quietly used with neither error nor warning. The convention of prefixing define names with the chart name is not meant to look pretty but to prevent this collision.
tpl, which re-renders strings inside values
A function often used as a partner to libraries is tpl. A string written in values is, by default, just characters, so even if it contains braces it is printed as it is.
note: "{{ .Release.Name }} in {{ .Release.Namespace }}"
If you plug this value in with {{ .Values.note }}, the braces go out as they are. If you plug it in with {{ tpl .Values.note . }}, it is interpreted once more as a template on the spot and the release name goes in. Charts where users pass free-form configuration through values use this approach — things like a bundle of annotations, a sidecar definition, or the body of a configuration file. In exchange, it means values can execute templates, so you must not pass untrusted values to tpl.
When to use it and when not to
The conditions under which a library chart is the answer are surprisingly narrow. It is when several charts must follow the same rule, and that rule will keep changing. Standard labels are exactly that — monitoring and policies pick targets by labels, so the rule has to be one, and as the company grows, team labels and cost-center labels get added one by one.
Conversely, if there are only two or three charts and the rules are settled, the cost of creating a library is greater than the gain. Dependency declarations increase, one more version-upgrade flow appears, and a newcomer has to open the tgz inside charts/ rather than _helpers.tpl to read the template. This last point is especially underestimated — when the rendered result looks odd, finding which file that template lives in gets one step farther away.
So what to decide before whether to adopt it is the version-upgrade flow. When you raise the library version, when do the consuming charts follow? If you pin the version range in Chart.yaml like 0.1.0, you have to raise the consuming charts one by one by hand, and if you leave it open like ^0.1.0, the result varies depending on when you run helm dependency update. If you have CI fetch only what the lock says with helm dependency build, the risk of the latter disappears — since the lock file is in the repository, which version you built with stays in the record.
What it looks like in the field
What you run into most often when adopting a library chart is the update timing. helm dependency update takes the library at that moment as a tgz and puts it in the parent's charts/. So even if you fix one label line in the library, the old copy keeps being used until the consuming chart fetches its dependencies again. If you use an in-house repository, you need a flow where you raise the library version, adjust the version range in the consuming charts' Chart.yaml, and have CI fetch exactly what the lock says with helm dependency build.
And it is better not to put too much in the library. Making a whole Deployment common looks clean at first, but as each chart develops its own different requirements one by one, if statements pile up. The boundary that holds up well in practice is usually up to labels, naming rules, and common annotations, with each chart keeping the workload body itself. Where the gain from commonality and the cost of branching flip differs by team, so when the library starts to grow, you have to stop once and measure.
What you will do in the next lab
You create a library chart yourself and confirm that installation is refused, then make two application charts use the same Deployment template and label rules by changing only the values. You revive a template string inside values with tpl, and finally, when the parent redefines the same name, you place the two charts side by side to check which one is rendered.