skip to content

Your chart passes helm lint and helm template in CI, but helm install fails on an invalid object name. Why?

level: seniorimportance: should knowfreq 44%

answer

  1. Neither check talks to an API server
  2. CI rendered under a short placeholder name
  3. Truncation happened before the suffix was added
  4. Sixty-three characters, names and label values
  5. One dry run that the server evaluates

basics

~20 s

Neither command validates rendered objects against a Kubernetes API server, and helm template uses a short placeholder release name unless you pass one. A name helper that truncates to 63 characters and then has a suffix appended only overflows under a real, long release name.

solid answer

~50 s

Both checks are client-side. `helm lint` parses the rendered text as YAML; `helm template` prints it. Neither knows that Kubernetes caps most object names at 63 characters for DNS-label kinds, or that label values have the same cap. The second half of the trap is the release name: unless you pass one, `helm template` substitutes a short default placeholder, so the classic `{{ include "chart.fullname" . }}` helper — which truncates to 63 and trims a trailing dash — never approaches the limit in CI. Install with a 38-character release name and the truncated fullname is already 54 characters; a template that appends `-broadcast-headless` to it produces a 73-character Service name and the API server rejects it. The fix is two-part: render with the longest release name you support, and add a server-side dry run so the API server itself validates the manifests without persisting anything.

code

bash · 5 lines
bash
helm template chat-fanout-prod-eu-central-1-shard-07 ./charts/fanout-operator \
  --namespace chat --include-crds > rendered.yaml

helm install chat-fanout-prod-eu-central-1-shard-07 ./charts/fanout-operator \
  --namespace chat --dry-run=server

go deeper

for a junior

Know that rendering a chart is not the same as installing it, and that the release name you pass changes every generated object name. Try rendering the same chart under a short and a long release name and compare.

for a middle

Explain why the checks pass: both are client-side text checks, and the placeholder release name keeps derived names short. Be able to point at the truncation-then-suffix pattern as the source of the overflow.

for a senior

Demonstrate the fix as a suite change, not a one-off patch: render at the longest supported release name, assert on name and label-value lengths across all objects, and place a server-side dry run where credentials make it affordable.

for a principal

Frame the three validators — input schema, rendered text, API server — and decide where each belongs in the delivery path, what it costs, and which failures you are content to discover after merge rather than before.

## The two blind spots A pull-request check that renders a chart is checking *text*. `helm lint` executes the templates and parses the result as YAML; `helm template` executes them and prints the result. Neither has a model of Kubernetes objects, and by default neither speaks to an API server. Everything the API server enforces — name syntax and length, label-value syntax and length, unknown fields, immutability, admission — is downstream of both. The second blind spot is subtler and is what makes this failure feel unfair: **the release name in CI is not the release name in production.** `helm template ./charts/fanout-operator` with no NAME argument substitutes a short default placeholder release name into `.Release.Name`. Every name your chart derives from it is therefore short in CI and long in production. ## The worked failure Take an operator chart for a chat fan-out service. The chart is named `fanout-operator` (15 characters) and uses the conventional helper: if the release name already contains the chart name use it, otherwise join them with a dash, then `trunc 63 | trimSuffix "-"`. - In CI, the placeholder release name is around a dozen characters. Fullname lands near 28 characters. A template that writes `{{ include "fanout-operator.fullname" . }}-broadcast-headless` produces a 47-character Service name. Valid, renders, lint is green. - In production the release is `chat-fanout-prod-eu-central-1-shard-07` — 38 characters. Fullname is 38 + 1 + 15 = 54 characters, comfortably under the helper's truncation. Appending the 19-character suffix gives **73 characters**, and the API server rejects the Service: names of that kind must be at most 63 characters. The bug is not the truncation, it is that the truncation happened *before* the suffix was appended. The helper guarantees 63; every caller that decorates its output is on its own. The same shape bites label values: `app.kubernetes.io/instance` is commonly set straight from `.Release.Name`, and a label value is also capped at 63 characters. Helm bounds the worst case for you, which is worth knowing: it refuses release names longer than 53 characters. So the longest name you must ever render against is knowable, not hypothetical. ## What to change in the pre-merge suite **Render with the names you actually use.** Pass an explicit, maximally long release name and namespace to `helm template` in CI. This one change catches the whole family of truncation defects and costs nothing. **Assert on the rendered output, not just on its exit code.** A render job that only checks that the command succeeded proves the templates execute. Add assertions: extract every `metadata.name` and every label value and fail if any exceeds 63 characters; check the objects you promise exist actually appear. These are the assertions that catch the class of defect lint structurally cannot. **Add a server-side dry run.** This is the only client-side-looking check that gets real API-server validation: the manifests are sent to the API server, which validates them — schema, unknown fields, name syntax, defaulting and admission — and nothing is persisted. It needs cluster credentials, so it belongs after the free checks and often after merge, but it is the step that turns *this renders* into *this would be accepted*. **Remember the CRDs.** An operator chart's `crds/` directory is not templated and is left out of `helm template` output unless you pass `--include-crds`. A render-assertion suite that greps for the chart's CustomResourceDefinitions and finds none is usually missing that flag, not missing the CRDs. ## Version note that matters here In Helm 4, `--dry-run` takes a value: `client` or `server`. `helm template` additionally marks `--validate` and `--dry-run` mutually exclusive and deprecates `--validate` in favour of `--dry-run=server`. Helm 3.13 and later already accept `--dry-run=server` on install and upgrade, so a pipeline written against either line can use the same wording. ## Saying it in an interview The strong answer separates three validators that candidates routinely merge into one word. A values schema validates *inputs*. Lint and template validate *text*. The API server validates *objects*. A chart that passes the first two and fails the third has not found a Helm bug; it has found the boundary between them, and the pre-merge suite should be built to make that boundary visible early.

  • How long may a Helm release name be, and why does that bound the test?
    Helm refuses a release name longer than 53 characters. That turns the worst case into a known quantity: render your chart once against a 53-character release name and you have covered every name any caller can create. Without that bound you would be guessing at how long is long enough.
  • What does a server-side dry run catch that a client-side render cannot?
    Everything the API server owns: object schema validation, unknown or misspelled fields, name and label-value syntax, defaulting and mutating admission, and rejections from validating admission. The manifests are sent to the server and evaluated without being persisted. The cost is that it needs credentials and a reachable cluster, so it is not a free pull-request check.
  • Your render-assertion job reports zero CustomResourceDefinitions for an operator chart. What is wrong?
    Almost certainly the missing `--include-crds` flag: `helm template` leaves the `crds/` directory out of its output by default. Those files are never templated either, so no values combination affects them and no lint rule exercises them — if you want them asserted on, ask for them explicitly.
  • How would you assert on rendered names without hand-writing a check per resource?
    Render to a single file, then walk every document and fail on any `metadata.name` over 63 characters or any label value over 63. It is one generic assertion covering every object the chart will ever add, which is far more durable than a per-resource golden file that only fails once someone remembers to regenerate it.

saying these in an interview costs you the question

  • Claims helm template validates against the cluster by default
  • Renders only with the default placeholder release name
  • Assumes a helper truncating to 63 makes every derived name safe
  • Confuses values-schema validation with API-server validation
  • Treats a green lint as proof the chart installs
  • Expects helm template output to include the crds directory

context