skip to content

What does `helm env` print, and how do you use it when Helm behaves differently on two machines?

level: middleimportance: should knowfreq 52%

answer

  1. One command prints the resolved client settings
  2. Three directory roots, not one home
  3. Cache, config and data are separate
  4. Repository cache and plugins derive from them
  5. Diff the output from both machines

basics

~20 s

helm env prints the client's fully resolved settings as shell-style assignments: the cache, config and data home directories, the repository cache and config paths, the plugin directory, the registry credentials file, and the resolved namespace. It contacts no cluster.

solid answer

~40 s

`helm env` dumps the Helm client's resolved environment — every `HELM_*` setting after flags, environment variables and defaults have been applied — as `KEY="value"` lines. The interesting entries are the three roots: `HELM_CACHE_HOME`, `HELM_CONFIG_HOME` and `HELM_DATA_HOME`, which default to OS-specific directories under the user's home. From them Helm derives `HELM_REPOSITORY_CACHE` (downloaded index files and chart archives), `HELM_REPOSITORY_CONFIG` (the `repositories.yaml` listing configured repos), `HELM_REGISTRY_CONFIG` (OCI credentials) and `HELM_PLUGINS`. It also prints resolved behaviour settings such as `HELM_NAMESPACE`, `HELM_DEBUG` and `HELM_MAX_HISTORY`. Because it is purely local — no API call — it works even against an unreachable cluster, which makes it the first command to run when the same chart, repo or plugin behaves differently on a laptop and a CI runner: capture the output on both and diff it.

code

bash · 9 lines
bash
# Capture the resolved client environment on both machines and compare
helm env | sort > helm-env-laptop.txt
ssh ci-runner 'helm env | sort' > helm-env-ci.txt
diff helm-env-laptop.txt helm-env-ci.txt

# Move every Helm directory into one job-local workspace
export HELM_CACHE_HOME="$PWD/.helm/cache"
export HELM_CONFIG_HOME="$PWD/.helm/config"
export HELM_DATA_HOME="$PWD/.helm/data"

go deeper

for a junior

Know that this command exists and prints paths, not cluster information. Being able to say where your repository list and plugins live already puts you ahead on environment questions.

for a middle

Explain the three roots and which derived path each one owns, and be able to name the settings you would check for an unknown repository, a missing plugin, or a registry login that did not stick.

for a senior

Demonstrate the diff-the-two-environments habit, and name the real causes — a different HOME under sudo or in a container, a baked runner image, a partially overridden set of roots.

for a principal

Own the policy: make Helm's directories explicit and job-local in CI so runs are reproducible, and decide what gets cached between runs versus refreshed, since that choice is what makes builds either fast or wrong.

### What the command is for `helm env` answers one question: *what settings is this Helm client actually using right now?* It prints the resolved values after every layer has been applied — built-in defaults, then environment variables, then anything a flag on that invocation changed — formatted as shell assignments, one per line, so the output can be logged, diffed or pasted into a bug report. It makes no API call. That is worth knowing for two reasons: it works when the cluster is unreachable, and it therefore proves nothing about whether your credentials are valid. ### The three roots Helm 3 replaced Helm 2's single `HELM_HOME` with three separate directories that follow the usual OS conventions, and Helm 4 kept them: - `HELM_CACHE_HOME` — throwaway data Helm can re-download. On Linux this defaults under `~/.cache/helm`. - `HELM_CONFIG_HOME` — configuration you would be annoyed to lose. On Linux, under `~/.config/helm`. - `HELM_DATA_HOME` — installed artefacts, chiefly plugins. On Linux, under `~/.local/share/helm`. On macOS these resolve under `~/Library` instead. The split matters operationally: you can wipe the cache root at any time with no loss, but wiping the config root loses your repository list, and wiping the data root loses your installed plugins. ### The derived paths Four of the printed entries are normally derived from those roots, and each is the answer to a specific "why does this machine behave differently" question: - `HELM_REPOSITORY_CONFIG` — the `repositories.yaml` recording which chart repositories this user has added. A different value here is why `helm install myrepo/chart` says the repository does not exist on one machine and works on another. - `HELM_REPOSITORY_CACHE` — where downloaded index files and chart archives land. A different (or older) directory here is why two machines resolve different chart versions from the same repository name. - `HELM_PLUGINS` — where plugins are installed. A plugin subcommand that exists on your laptop and "is not a helm command" in CI is nearly always this. - `HELM_REGISTRY_CONFIG` — the credentials file used for `oci://` references, which is why a registry login performed as one user does not help a process running as another. Each of those can be set explicitly, in which case it wins over the value derived from its root. Setting the root moves everything under it; setting the specific variable moves just that one thing. ### The behaviour settings The output also carries resolved runtime settings — among them `HELM_NAMESPACE` (the namespace this invocation would target), `HELM_DEBUG`, `HELM_MAX_HISTORY`, and the kube-connection settings that mirror the corresponding flags. `HELM_NAMESPACE` in particular makes `helm env` a cheap pre-flight: run it before an upgrade and you can see what Helm resolved rather than what you assumed. ### Using it as a diagnostic The standard move when "it works on my machine" is to capture `helm env | sort` on both machines and diff the two files. Recurring causes the diff exposes: - **A different user, therefore a different home.** A pipeline step that runs Helm under `sudo`, or a container whose `HOME` is `/root` while the image baked repository config under a build user, gets an empty repository list and no plugins even though the files are visibly present on disk. - **A pre-baked image.** A runner image that shipped a warm cache carries whatever index files existed at image-build time, forever, until something refreshes them. - **A partially overridden set.** Someone exported `HELM_CACHE_HOME` in a CI job but not `HELM_DATA_HOME`, so charts cache in the shared volume and plugins do not — the classic "the plugin reinstalls on every run" symptom. - **Two binaries.** `HELM_BIN` tells you which executable is speaking, which is how you discover a shell alias or a second install earlier on `PATH`. ### Worked example A subscription-billing cron chart guards its inputs with a `values.schema.json` contract and an 11-value production override file. A developer renders it fine; the CI job fails claiming the repository is unknown. `helm env` on the runner shows `HELM_CONFIG_HOME` pointing at `/root/.config/helm` while the image's `helm repo add` ran as the build user — so `repositories.yaml` exists, just not where this process looks. The fix is to set the roots explicitly in the job (or re-add the repository as the running user) rather than to re-add repositories by trial and error. ### Hygiene Print `helm env` into CI logs on every deploy job. It is a handful of lines, it costs nothing, and it turns the next environment-drift incident from an argument into a diff.

  • How would you point Helm at a throwaway configuration directory for a single command?
    Set the roots inline for that invocation — `HELM_CACHE_HOME=... HELM_CONFIG_HOME=... HELM_DATA_HOME=... helm ...` — or override just the specific path, such as `HELM_REPOSITORY_CONFIG`, when only one thing should move. An explicitly set specific variable beats the value derived from its root, so you can relocate the whole tree or one file. This is the usual way to give a CI job an isolated repository list that cannot be polluted by whatever the image baked in.
  • Does a clean `helm env` output tell you the cluster connection is working?
    No. The command resolves local settings and prints them without contacting the API server, so it happily reports a namespace and a context name for a cluster that is unreachable or a credential that has expired. It answers "what would Helm use", not "does it work". To test reachability you need a command that actually talks to the cluster.

saying these in an interview costs you the question

  • Thinks helm env queries the cluster or reports its state
  • Expects a single HELM_HOME directory as in Helm 2
  • Assumes plugins live beside the helm binary
  • Believes the repository list is machine-wide, not per-user
  • Says the cached index is refreshed automatically

context