skip to content

Chart Structure

What a chart is on disk and how it becomes a versioned artifact: metadata that identifies it, templates that render it, defaults that parameterise it, and a tarball others install.

part ofHelmoverview, primer and where to startread it →
on this pageshow

explore

questions

20

In a Helm Chart.yaml, what is the difference between version and appVersion?

level: juniorimportance: must knowfreq 84%

answer

  1. Two versions, two different things
  2. One is the package, one is the payload
  3. Only one must be valid SemVer 2
  4. One names the .tgz and the repo index
  5. The other lands in app.kubernetes.io/version

basics

~20 s

version is the chart package's own version and must be valid SemVer 2; Helm names the tarball, indexes and resolves the chart from it. appVersion is a free-form label naming the application release the chart ships, and Helm never parses it.

solid answer

~40 s

`version` in `Chart.yaml` identifies the *packaging artifact*. It must be SemVer 2, `helm package` names the tarball `<name>-<version>.tgz` from it, a chart repository indexes entries by name plus version, and every publishable change to the chart needs a bump. `appVersion` identifies the *thing being packaged* — the release of the application the chart deploys. It is optional, free-form and never parsed or compared by Helm, so it should be quoted (`appVersion: 1.20` unquoted is a YAML float and renders as `1.2`). It surfaces in `helm list`'s APP VERSION column and in templates as `.Chart.AppVersion`, which the `helm create` scaffold writes into the `app.kubernetes.io/version` label and uses as the default image tag. The two move independently: fixing a probe path bumps `version` alone; shipping a new application build bumps both.

code

yaml · 6 lines
yaml
apiVersion: v2
name: tile-server
description: A geospatial tile server
type: application
version: 2.14.3
appVersion: "7.9.2"

go deeper

for a junior

Be able to say in one sentence which field describes the chart and which describes the application, and that only the chart's version has to be SemVer. Knowing the two columns helm list prints is enough to pass this screen.

for a middle

Explain the mechanics: what helm package names the tarball from, why a repository cannot serve two different tarballs at one version, and where .Chart.AppVersion ends up in the scaffold's templates and labels.

for a senior

Show the release discipline. Say when each field moves, why a published chart version is effectively immutable, and how a version label sourced from appVersion can end up lying once a caller overrides the image tag.

for a principal

Own the policy: who is allowed to cut a chart version, whether charts version with the application or independently, and how the chart version, the application version and the release revision are correlated across a fleet during an incident.

## Two versions in one file, two different questions `Chart.yaml` is a Helm chart's metadata file, and it carries two fields that both look like a version. `version` is the version of the *chart* — the packaged directory of templates, defaults and helpers. `appVersion` is the version of the *application* that chart installs. Helm ships two fields rather than one because a chart and the software it deploys have independent release cycles: you can fix a chart bug without rebuilding the application, and you can ship a new application build without touching a single template. | Axis | `version` | `appVersion` | |---|---|---| | Describes | the chart package | the application it installs | | Required, and in what form | yes, valid **SemVer 2** | no, and free-form | | Parsed by Helm | yes | never | | Where it surfaces | tarball name, `index.yaml`, CHART column | APP VERSION column, `.Chart.AppVersion` | ## `version` is the packaging artifact Helm requires this field and requires it to be valid SemVer 2 — `MAJOR.MINOR.PATCH`, optionally with a prerelease or build suffix. A chart whose `version` is not parseable SemVer will not load. Everything mechanical hangs off it: `helm package` writes `<name>-<version>.tgz`, a chart repository's `index.yaml` keys each entry by name and version, a version constraint that another chart puts on this one resolves against it, and each release revision records the chart version it was installed from, which is what `helm history` and `helm list`'s CHART column show. Because publishing is keyed on name plus version, `version` is effectively **immutable once published**: a repository already serving `tile-server-2.14.3.tgz` will not notice different content republished under the same coordinates, and consumers get whichever tarball their cache saw first. So every publishable change to the chart — a template fix, a changed default in `values.yaml`, a new helper, a re-pinned subchart — needs a bump. ## `appVersion` is the payload label It is optional, **free-form**, and Helm never parses, compares or sorts it. "Free-form" is literal: `9.2-beta`, `2024.11.3`, a commit SHA, or the string `latest` are all accepted. Because it is free-form it must be **quoted** in YAML, which is why the `helm create` scaffold writes it quoted — an unquoted `appVersion: 1.20` is parsed as a float and renders as `1.2`, and that silently wrong version then travels into a label. What `appVersion` does do is *surface*. `helm list` prints it in the APP VERSION column beside CHART; `helm show chart` prints it; and templates read it as `.Chart.AppVersion`. The scaffold uses it twice: the standard label `app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}`, and as the fallback for the container image tag, so a chart ships a known application build by default while a caller can still override the tag through values. ## How they move together, and how they don't Take a geospatial tile server packaged as chart `tile-server`, currently `version: 2.14.3` / `appVersion: "7.9.2"`. Four cases cover almost every real change: - You correct a readiness probe path in a template. The application is untouched: bump `version` to `2.14.4`, leave `appVersion`. - The tile server team ships build 7.9.3 and the chart needs no edits. Bump both — `appVersion` because the payload moved, `version` because changing `Chart.yaml` at all is a change to the chart, and a repository cannot serve two different `2.14.3` tarballs. - You rename a values key in a way consumers must react to. Bump `version` to `3.0.0`; `appVersion` is irrelevant to that decision, because SemVer here describes the chart's own interface, not the application's. - Nothing changes. Do not re-cut a tarball under an existing `version`; that is the one move that makes a chart repository lie. ## The confusion the question is testing "The version" is one of the most overloaded words around Helm: the chart version, the application version, the `apiVersion` a rendered manifest declares, the Kubernetes version the cluster reports, and the Helm CLI's own version are five different things. A candidate who answers "`version` is the release number" has also collapsed a sixth — the **release revision**, `.Release.Revision`, an integer that Helm increments on every `helm upgrade` and `helm rollback` regardless of which chart version was used. A single release of a 41-service platform chart can sit at revision 38 while the chart is at `2.14.3` and the application at `7.9.2`, and the three numbers have nothing to do with each other. The practical rule to state in an interview: bump `version` for anything you would publish, bump `appVersion` when the software inside changes, and never treat `appVersion` as something Helm can reason about — it is documentation that happens to be machine-readable.

  • If only appVersion changes, does anything about the deployed objects change?
    Only if a template reads `.Chart.AppVersion`. In the standard scaffold it does, twice: the `app.kubernetes.io/version` label and the default image tag, so the rendered manifest changes and the upgrade produces a real diff. If a chart pins the image tag in `values.yaml` instead, moving `appVersion` alone changes nothing but the metadata Helm prints — and that mismatch, a version label disagreeing with the running image, is a common source of confusion during an incident.
  • Is defaulting the container image tag to .Chart.AppVersion a good idea?
    It is the scaffold's default and it keeps chart and application shipping together, which is usually what you want. The costs: a tag is mutable, so it does not pin content the way a digest does; and once a caller overrides the tag, the `app.kubernetes.io/version` label still reports `appVersion` and now lies about what is running. Charts that care usually derive both the tag and the label from the same value so they cannot diverge.
  • Why does helm create write appVersion quoted?
    Because YAML types unquoted scalars. `appVersion: 1.20` is a float, and it renders as `1.2` — the trailing zero disappears into a label or an image tag and the mistake is invisible until someone looks for version 1.20 and cannot find it. Quoting keeps the exact string. `version` is safe unquoted only because SemVer 2 always has three dot-separated parts, which YAML cannot read as a number.

saying these in an interview costs you the question

  • Says appVersion must be valid SemVer like version
  • Bumps only appVersion after editing templates
  • Thinks version is the release revision number
  • Claims Helm compares appVersion when resolving charts
  • Leaves appVersion unquoted so 1.20 becomes 1.2
  • Republishes changed content under the same chart version

context

open as a page

How does Helm treat a chart's crds/ directory differently from templates/?

level: juniorimportance: must knowfreq 72%

basics

~10 s

Files under crds/ are plain YAML that Helm never renders as templates. Helm applies them before anything in templates/, skips any CustomResourceDefinition already present in the cluster, and never updates or deletes them.

open as a page

What does helm package produce, and what determines the archive's filename?

level: juniorimportance: must knowfreq 68%

basics

~20 s

helm package compresses a chart directory into a gzipped tar named <name>-<version>.tgz, with both parts read from Chart.yaml. The version must be valid SemVer 2, and the archive unpacks into one directory named for the chart.

open as a page

Which files under a Helm chart's templates/ directory do not become Kubernetes manifests?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Helm evaluates every file under templates/, but output from a file whose name begins with an underscore or a dot never becomes a manifest. NOTES.txt becomes the release notes, and a file rendering to only whitespace is dropped.

open as a page

What does `helm show values` print for a Helm chart, and why is values.yaml the chart's public API?

level: juniorimportance: must knowfreq 66%

basics

~20 s

helm show values prints a chart's values.yaml exactly as the author wrote it, comments included, without installing or rendering anything. That file is the chart's default surface: every key a consumer may override, and the value they get if they do not.

open as a page

Why does helm upgrade leave a chart's crds/ CustomResourceDefinition untouched?

level: middleimportance: must knowfreq 64%

basics

~20 s

Helm has no safe way to change a CustomResourceDefinition — an edit can invalidate stored objects and a delete takes every custom resource with it. So crds/ is install-only: upgrades skip it, and a new chart version's schema change never lands.

open as a page

Why does a misspelled key in a Helm values file cause no error, and what makes Helm reject it?

level: middleimportance: must knowfreq 71%

basics

~20 s

Helm merges caller values onto chart defaults as an untyped map, so an unknown key is simply added and never read - the template still sees the default and the release installs cleanly. A values.schema.json with additionalProperties: false turns that typo into a failed install.

open as a page

In a Helm Chart.yaml, what does apiVersion: v2 declare, and which fields are mandatory?

level: middleimportance: should knowfreq 56%

basics

~20 s

apiVersion: v2 marks the chart as the modern chart format used by Helm 3 and Helm 4: dependencies are declared inside Chart.yaml and the type field exists. Only apiVersion, name and version are mandatory; every other field is optional metadata.

open as a page

What does a chart's .helmignore file exclude, and when does it have no effect?

level: middleimportance: should knowfreq 52%

basics

~20 s

.helmignore lists glob patterns, one per line, for files Helm leaves out when it loads a chart from a directory — packaging or installing from source. It never applies to an already-packaged .tgz, whose contents were fixed at package time.

open as a page

In a helm create chart, why does the fullname helper truncate to 63 characters?

level: middleimportance: should knowfreq 48%

basics

~20 s

The generated name is used as a Kubernetes label value and often as a Service name, and both are capped at 63 characters. The paired trimSuffix "-" removes a hyphen the cut can leave, which would be illegal.

open as a page

What does kubeVersion in a Helm Chart.yaml do, and when is it checked?

level: seniorimportance: should knowfreq 36%

basics

~20 s

kubeVersion is an optional SemVer range of Kubernetes versions the chart supports. Helm compares it against the version it has in hand whenever it renders the chart and refuses outright if the constraint is not satisfied, rather than warning.

open as a page

Your Helm chart's new crds/ field works on fresh clusters but not on upgraded ones — why?

level: seniorimportance: should knowfreq 46%

basics

~20 s

crds/ is applied only by helm install, so a cluster that already ran the release keeps the old definition and validates new custom resources against it — the added field is pruned or rejected. Apply the definition, then upgrade.

open as a page

Two helm package runs over identical chart source produce .tgz files with different digests. Why?

level: seniorimportance: should knowfreq 36%

basics

~20 s

A chart archive is a tar, and tar entries carry each file's modification time, so a fresh checkout changes the bytes even when the content is identical. Helm 4's helm package honours SOURCE_DATE_EPOCH: pin it and the archive becomes byte-reproducible.

open as a page

A chart's selectorLabels helper was changed to include the chart version - what breaks on upgrade?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Every chart-version bump now changes the workload's label selector, and a selector is immutable after creation. The next upgrade is rejected by the API server, the release fails, and no flag gets past it: the object must be deleted and recreated.

open as a page

When does Helm enforce values.schema.json, and how does it apply to a chart's subcharts?

level: seniorimportance: should knowfreq 43%

basics

~20 s

Helm validates the coalesced values against values.schema.json before rendering - on install, upgrade, template and lint. Every chart in the dependency tree is checked against its own schema over the slice of values scoped to it, and the failures are reported together.

open as a page

When would you ship a Helm chart's CRDs as a separate chart rather than in crds/?

level: principalimportance: should knowfreq 34%

basics

~20 s

Split them out when the definitions are shared by several releases, must be upgradeable, and are written by a different identity than the application. Keeping them in crds/ is right when one team owns one release per cluster and the schema is stable.

open as a page

In a platform's 18-chart Helm umbrella, how strict would you make each chart's values.schema.json?

level: principalimportance: should knowfreq 31%

basics

~20 s

Strict where the chart makes a promise, open where it passes values through. Type and close the objects the chart owns, leave subchart and global subtrees to their own schemas, and treat tightening a published schema as a breaking chart version rather than a patch.

open as a page

What is the annotations map in a Helm Chart.yaml for, and who reads it?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

Chart.yaml's annotations is a free-form string map of chart-level metadata. Helm itself does nothing with it: it carries the map into the repository index so catalogues can read it without downloading the chart, and exposes it to templates as .Chart.Annotations.

open as a page

How does a chart's templates/NOTES.txt reach the user, and how do you read it later?

level: middleimportance: nice to knowfreq 32%

basics

~20 s

templates/NOTES.txt is rendered like any other template, but its output is printed to the operator after install or upgrade instead of being applied to the cluster. Helm stores it with the release, so helm get notes returns it later.

open as a page

What does helm create scaffold under templates/, and which file is new in Helm 4?

level: middleimportance: nice to knowfreq 24%

basics

~10 s

It writes deployment.yaml, service.yaml, serviceaccount.yaml, ingress.yaml, hpa.yaml, NOTES.txt, _helpers.tpl and tests/test-connection.yaml. Helm 4 adds httproute.yaml beside ingress.yaml, giving the scaffold a second routing option that is off until its values toggle is set.

open as a page