skip to content

Packaging a Chart

helm package turns a directory into a name-version.tgz, with .helmignore deciding what stays out and helm lint run first. Asked because a chart is a versioned artifact, not a folder of YAML.

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

questions

4

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

level: juniorimportance: must knowfreq 68%

answer

  1. A chart ships as one file
  2. The filename is derived, not chosen
  3. Two Chart.yaml fields decide it
  4. name-version.tgz, SemVer 2 enforced
  5. One top-level directory inside

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.

solid answer

~40 s

`helm package ./pdfsign` reads `Chart.yaml`, validates the metadata and writes `pdfsign-2.7.3.tgz` into the working directory; `-d/--destination` sends it elsewhere. The filename is a contract, not a label: it is always `<name>-<version>.tgz`, and both halves come from `Chart.yaml`, so the directory name on disk is irrelevant. `version` must parse as SemVer 2 or packaging fails, and `--version` / `--app-version` override those two fields at package time, which is how CI stamps a build without editing the file. `-u/--dependency-update` resolves declared dependencies into `charts/` first, so an optional bundled database subchart is vendored inside the tarball. Everything under the chart directory is included except what `.helmignore` excludes, and the archive expands to a single top-level directory named after the chart. Run `helm lint` first — `helm package` is not a linter and never renders a template.

code

bash · 5 lines
bash
helm lint ./pdfsign
helm package -u -d ./dist ./pdfsign
# ./dist/pdfsign-2.7.3.tgz
helm show chart ./dist/pdfsign-2.7.3.tgz
helm install pdfsign ./dist/pdfsign-2.7.3.tgz

go deeper

for a junior

Be ready to say what the command outputs and where the name comes from: a <name>-<version>.tgz built from the name and version fields of Chart.yaml, written to the current directory unless you pass a destination.

for a middle

Explain the mechanics around it: SemVer 2 enforcement on version, the version/appVersion split, -u vendoring subcharts into charts/, the single top-level directory inside the archive, and the fact that packaging never renders a template.

for a senior

Show the pipeline judgement: lint and render before packaging, package into a clean output directory, keep the version bump in the same commit as the change, and treat a published archive as immutable rather than rebuilding over it.

for a principal

Own the artifact policy: who is allowed to cut a chart version, how chart versions relate to application releases across many teams, and whether charts are versioned per-service or as one platform bundle.

### A chart is an artifact, not a folder During development a chart is a directory: `Chart.yaml`, `values.yaml`, `templates/`, maybe `charts/` and `crds/`. `helm package` turns that directory into the distributable form of the same thing — a gzip-compressed tar archive. Everything downstream of packaging (a chart repository, an `oci://` reference, an air-gapped copy on a USB stick, `helm install ./pdfsign-2.7.3.tgz`) deals in that archive rather than in your working tree. Interviewers ask about it because the difference between "a folder of YAML I happen to have" and "a versioned artifact somebody else can pin" is the whole reason Helm exists. ### The filename is derived, not chosen The output is always `<name>-<version>.tgz`, where `name` and `version` are the two required fields of `Chart.yaml`. A chart directory called `pdf-signer-chart` whose `Chart.yaml` says `name: pdfsign` and `version: 2.7.3` packages to `pdfsign-2.7.3.tgz`. `helm lint` flags a directory whose name disagrees with the chart name, and that is worth fixing because humans read the directory and tools read `Chart.yaml`. `version` is the chart's own version and must be a valid SemVer 2 string; packaging fails outright on a value Helm cannot parse. `appVersion` — the version of the software the chart deploys — is free-form and has no effect on the filename. That split is what lets you ship `pdfsign-2.7.4.tgz` as a template-only fix with `appVersion` unchanged. Inside the tarball there is exactly one top-level directory, named after the chart, with the chart files beneath it. Helm's loader relies on that shape, which is why you cannot simply `tar czf` a directory's *contents* and expect Helm to read it. ### The flags that actually come up - `-d, --destination` — write the `.tgz` somewhere other than the current directory (in CI, a dedicated output dir). - `-u, --dependency-update` — resolve the dependencies declared in `Chart.yaml` into `charts/` before packaging, so subcharts travel inside the archive. A chart for a PDF-signing service with an optional bundled database subchart carries that subchart's `.tgz` under `charts/`; the toggle that decides whether it is *installed* is a value, but the bytes are in the package either way. - `--version` / `--app-version` — override the two `Chart.yaml` fields for this build. A pipeline that packages `2.7.3-rc.14` from a release branch without committing that string uses these. - `--sign` and its key flags exist too, but signing a package into a `.prov` file is a separate concern from producing the `.tgz` itself. ### What packaging does *not* do This is where junior answers go wrong. `helm package` does not render templates, does not contact a cluster, does not validate that your `Deployment` is legal Kubernetes, and does not publish anything. It checks the chart metadata, collects the files the loader can see, and writes an archive. A chart with a template that fails to parse packages happily and explodes at install time — which is why `helm lint` (and, better, a render in CI) belongs in front of it. It also does not upload. Making the archive discoverable — building or updating a repository index, or pushing it to an OCI registry — is a distinct step after packaging. ### Consuming the result A packaged chart is a first-class input everywhere a directory is: `helm install pdfsign ./pdfsign-2.7.3.tgz`, `helm template`, `helm show chart pdfsign-2.7.3.tgz`, `helm upgrade`. Because the chart's identity lives in `Chart.yaml` *inside* the archive, renaming the file does not rename the chart — but repository tooling expects the canonical name, so do not rename it. One more consequence worth knowing: the archive is a snapshot. `.helmignore` decided what went in at package time, and nothing you change in the source tree afterwards affects a `.tgz` that already exists. If the wrong file got in, you fix the ignore rules and cut a new version — you never edit a published archive in place. ### Reading a chart you were handed The same properties make an unfamiliar archive easy to interrogate without installing it. `helm show chart` prints the metadata, `helm show values` prints the defaults, and listing the tar contents tells you what files came along — a chart that ships 18 MB of sample documents or a stray developer values file announces itself immediately. Doing that once, before a chart enters a production path, catches most of what goes wrong at packaging time. ### A workable habit In CI: lint, render, package into a clean output directory, then hand the `.tgz` to whatever publishes it. Keep the version bump in the same commit as the change, so the artifact name alone tells you what is inside it.

  • What does --app-version change, and why would a pipeline set it?
    It overrides the `appVersion` field written into the packaged chart, which describes the software being deployed rather than the chart itself. A pipeline that builds an image tagged 5.14.3 can package the chart with `--app-version 5.14.3` so the artifact records what it ships, without a commit that edits `Chart.yaml`. It does not affect the archive filename — only `--version` does that.
  • Your chart declares an optional bundled database subchart. What actually ends up inside the .tgz?
    The subchart archive is vendored under `charts/` and travels inside the package, so the tarball is self-contained and installs without network access to the dependency's repository. `-u` resolves dependencies before packaging; without it you package whatever is already in `charts/`, which may be stale or empty. Whether the subchart is installed at runtime is a values decision, not a packaging one.
  • Can you rename pdfsign-2.7.3.tgz and still install it?
    Locally, yes — Helm reads the chart's identity from the `Chart.yaml` inside the archive, so `helm install` from an odd filename works. But repository tooling and any digest-keyed reference expect the canonical `<name>-<version>.tgz`, so renaming is a good way to produce an artifact nobody can resolve later. Treat the name as generated output.

The chart directory is the recipe you are still editing; helm package is sealing it into a labelled tin, where the label is printed from the recipe itself rather than written on by hand.

saying these in an interview costs you the question

  • Thinks the .tgz filename is cosmetic and can be anything
  • Says the archive name comes from the chart directory name
  • Assumes helm package renders templates and catches template errors
  • Believes packaging also publishes or signs the chart
  • Confuses version with appVersion when naming the artifact
  • Expects a .helmignore edit to change an already-built archive

context

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

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

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