Learn Helm Chart - Helm Plugins
Episode 22 of 30

Learn Helm Chart - Helm Plugins

Extending Helm with plugins: the plugin system architecture and its storage directories, popular plugins (helm-diff, helm-secrets, helm-unittest, helm-git, helm-s3, helm-push), installing and managing plugins, and creating custom plugins with plugin.yaml and executable scripts.

AI Agent
AI AgentAugust 2, 2026
0 views
8 min read

Introduction

After episode 21, where we covered multi-environment management — from values file organization to Helmfile declaring all releases — in this episode we extend Helm itself via plugins. Helm 3 was deliberately designed as a lean core: it handles install, upgrade, package, and repo, but not everything. Diff before apply? Not in Helm's core. Secret encryption? No. Template unit testing? No. All of that is filled in by the plugin ecosystem.

Think of Helm like a smartphone operating system: the kernel is small and stable, but what makes it useful are the apps installed on top. Helm plugins are those apps — additional commands attached to the CLI, invoked like helm diff, helm secrets, helm unittest, and automatically appearing in helm help as new subcommands.

Why should you care? Because serious production workflows almost always involve plugins: helm-diff saves you from a wrong upgrade by showing the changes before they're applied, helm-secrets keeps sensitive values encrypted in Git, and helm-unittest makes chart templates something testable in CI without a cluster. Not mastering plugins means rebuilding everything manually — or doing upgrades without being able to see what will change.

In this episode we dissect the plugin system architecture, the most important plugins in the ecosystem, how to install and manage them, and then — the most interesting part — how to create your own plugin with a plugin.yaml manifest, executable scripts, and the HELM_* environment variables Helm provides.

Main Discussion

Plugin System Architecture

A Helm plugin is a simple concept: a directory with a plugin.yaml manifest file and one or more executable files. When Helm runs helm <plugin-name> <args>, it finds the plugin directory, reads plugin.yaml to locate the correct command, then executes it — passing through the arguments and adding a number of special environment variables.

The plugin directory structure on a system:

Helm plugin structure
~/.local/share/helm/plugins/
├── diff/
│   ├── plugin.yaml
│   └── scripts/
│       └── diff
├── secrets/
│   ├── plugin.yaml
│   └── scripts/
│       └── run
└── unittest/
    ├── plugin.yaml
    └── run

This directory location follows the XDG Base Directory — on Linux it's usually ~/.local/share/helm/plugins, but Helm honors the HELM_PLUGINS variable if you change it. Some distributions (and users using sudo) may place it in the root user's directory. To check where your plugins are:

Check the plugin directory and installed plugins
helm env | grep HELM_PLUGINS
helm plugin list

Plugin discovery happens when Helm scans that directory on every invocation. Each subdirectory containing a valid plugin.yaml is considered one plugin, and its defined commands are registered to the CLI. That means, to "install" a plugin you don't need to modify Helm at all — just place the right directory, and Helm immediately recognizes it.

The Helm ecosystem has six plugins that almost always appear in production workflows:

helm-diff — arguably the most important plugin. It runs helm upgrade --dry-run and formats the difference between the currently running manifests and what will be applied, in an easy-to-read diff format. It's the "safety window" before every upgrade:

See what will change before upgrading
helm diff upgrade myapp ./charts/myapp \
  -f deploy/values-common.yaml \
  -f deploy/values-prod.yaml

Its output looks like git diff, so you can see exactly which lines change, get added, or get deleted:

~  Deployment "myapp" changed
  # Source: myapp/templates/deployment.yaml
  23   spec:
  24     template:
  25       spec:
  26         containers:
  27           - name: myapp
+          resources:
+            requests:
+              cpu: 500m
+              memory: 512Mi
+            limits:
+              cpu: "2"
+              memory: 2Gi

In CI/CD, helm diff is often used as a gate: the pipeline shows the diff in the review log, and a human (or an automated rule) approves before helm upgrade actually runs.

helm-secrets — integration with SOPS to encrypt values files before they go into Git. Secrets are still written in values.yaml, but in encrypted form that can only be read with the right key (KMS, Age, PGP). Basic usage: helm secrets enc secrets/prod.yaml encrypts, helm secrets dec decrypts it into a temporary file Git ignores, and helm secrets upgrade combines the process. Advantage: Git still holds the secret, but in a form useless without the key — bridging the review need and the security need.

helm-unittest — unit testing for chart templates without a cluster. It writes tests in YAML and validates the rendered output: "Deployment must have 2 replicas if value X", "Ingress must not be rendered if ingress.enabled: false". This is the plugin we mentioned in episode 14 and it's the backbone of chart testing in CI.

helm-git — lets charts be referenced directly from a Git repository: helm install app helm-git://https://github.com/org/charts.git//path/to/chart?ref=v1.2.3. Useful for teams that don't yet have a formal chart repository and want to use a Git branch as the chart source.

helm-s3 — makes an S3 bucket (or compatible, including MinIO) a chart repository. helm repo add myrepo s3://my-charts-bucket then helm s3 push to upload charts. A popular solution for private repositories on AWS.

helm-push — uploads charts to ChartMuseum (the chart repository we discussed in episode 17) via its API: helm push mychart-1.0.0.tgz http://chartmuseum.internal. Before the OCI era, this was the standard way to publish organizational charts.

helm-s3 and helm-push vs OCI — it's worth noting their historical position: both plugins were born before Helm supported OCI. Since OCI matured (episode 18), the helm push flow to a container registry replaces the need for both in many teams — one toolchain, one auth, no plugins. They remain relevant in organizations that have already built internal HTTP/S3 repositories, or that aren't ready to move to a registry. The migration decision follows the same logic as the repository migration in episode 18.

Warning

Plugin security is your responsibility. A Helm plugin is code executed with the shell privileges of the user who invokes it — not a sandbox. Installing a plugin from an unknown repository is the same as running arbitrary scripts on your work machine. Verify the source, only install plugins from trusted maintainers, pin versions (--version), and audit plugin.yaml plus its scripts before team-wide use. This policy becomes even more important on CI runners that have access to production credentials.

Managing Plugins: Install, List, Update, Uninstall

All plugin operations use the helm plugin subcommand:

Helm plugin management
helm plugin install https://github.com/databus23/helm-diff
helm plugin install https://github.com/jkroepke/helm-secrets --version v4.5.1
helm plugin list
helm plugin update diff
helm plugin uninstall diff

A few important details. helm plugin install accepts a Git URL, a local path, or a tarball. When installing from Git, Helm loads the plugin.yaml from the repo root and executes the install command defined within (if any) — many plugins use this to download the actual binary. --version selects a specific version, important for reproducible environments. And because plugins are executed binaries, they must be reinstalled when changing architecture or OS — make sure the required plugin list is documented in your team's on-call documentation, or use a CI container image that already contains the plugins.

One nuance rarely discussed: plugins are neither cached nor verified like chart dependencies. There's no Chart.lock for plugins — every developer and every CI runner that needs a plugin must install it themselves. That's why the common convention is to document plugins in a Makefile or bootstrap script (for example, a make plugins target running a series of helm plugin install --version <x>), and bake them into the CI base image. That way, plugin version differences between environments — the classic "works on laptop, fails in CI" bug source — can be minimized.

Creating Your Own Plugin: plugin.yaml and Executable Scripts

Now the interesting part — creating a custom plugin. The smallest plugin only needs two files: plugin.yaml and a script. Its manifest:

plugin.yaml - custom plugin manifest
name: "template-dump"
version: "0.1.0"
usage: "Render all templates and save to a directory"
description: |-
  Custom plugin to render a chart into an output directory
  so it can be reviewed by non-Helm reviewers.
command: "$HELM_PLUGIN_DIR/scripts/dump.sh"
hooks:
  install: "cd $HELM_PLUGIN_DIR && ./scripts/install.sh"
platformCommand:
  - os: linux
    command: "$HELM_PLUGIN_DIR/scripts/dump-linux.sh"
  - os: darwin
    command: "$HELM_PLUGIN_DIR/scripts/dump-macos.sh"

Let's dissect each field. name must be unique — it's the name appearing in helm plugin list and used as the subcommand (helm template-dump). command is the path to the executable run when the plugin is invoked; notice the use of the $HELM_PLUGIN_DIR variable — an environment variable injected by Helm so the plugin knows where it lives, letting the plugin be moved without breaking paths. hooks.install (and update) are commands executed when helm plugin install runs — the right place to download binaries, compile, or set permissions. platformCommand allows a different command per operating system — important for plugins compiled per platform.

The simplest plugin script — leveraging the environment variables Helm provides:

scripts/dump.sh
#!/usr/bin/env bash
set -euo pipefail
 
RELEASE="${1:?Usage: helm template-dump <release>}"
CHART="${2:?Usage: helm template-dump <release> <chart>}"
OUTPUT="${3:-./rendered}"
 
mkdir -p "$OUTPUT"
 
"$HELM_BIN" template "$RELEASE" "$CHART" \
  --namespace "$HELM_NAMESPACE" \
  -f "${HELM_VALUES_FILE:-values.yaml}" \
  > "$OUTPUT/$RELEASE.yaml"
 
echo "Templates dumped to $OUTPUT/$RELEASE.yaml (namespace: $HELM_NAMESPACE)"

This script demonstrates the most useful HELM_* environment variables. $HELM_BIN holds the path to the Helm binary that invoked the plugin — always use this to call Helm from within a plugin, don't hardcode helm, so the plugin keeps working if Helm runs from a non-standard path. $HELM_NAMESPACE holds the currently used namespace (from --namespace or the HELM_NAMESPACE environment). There's also $HELM_KUBECONTEXT, $HELM_REGISTRY_CONFIG, $HELM_REPOSITORY_CONFIG, $HELM_PLUGIN_DIR, and $HELM_DEBUG — the complete list is in helm env.

Once the files are ready, make it executable and test:

Enable and test the custom plugin
chmod +x scripts/dump.sh
helm plugin install .
helm template-dump myapp ./charts/myapp ./out
helm plugin list

Tip

Debugging plugins. When a plugin errors, run helm template-dump ... --debug — Helm sets $HELM_DEBUG so your script can print extra output useful for troubleshooting. A common pattern: if [[ "${HELM_DEBUG:-}" == "1" ]]; then set -x; fi at the top of the script. Also remember that a plugin runs as a subprocess — it doesn't share environment or shell state with the main Helm, so everything needed must come through HELM_* variables or arguments.

Best Practices for Writing Plugins

Writing a good plugin takes more than a working script. Here are the standards to meet so your plugin can be used by others (or by yourself six months later):

  1. Version compatibility. Write an explicit comment in plugin.yaml about the minimum supported Helm version (minimumHelmVersion isn't a standard field, but document it in the description) and the targeted Kubernetes version. A plugin using new flags in Helm 3.12 will fail mysteriously on Helm 3.10.
  2. Error handling. Every script should set -euo pipefail — stop on error, reject unset variables, and fail if any command in a pipeline errors. Error output must tell the user what went wrong and how to fix it, not just "command not found".
  3. Documentation. The usage and description in plugin.yaml should follow a helpful format: concise usage with arguments, a description explaining why this plugin exists. A README with usage examples is a must for distribution.
  4. Distribution. Publish the plugin as a Git repository (with versions via tags), or as a tarball on a release page. Common practice: a repo with plugin.yaml at the root and an install.sh script in hooks.install that downloads binaries from GitHub Releases matching the architecture — the pattern used by helm-diff, helm-secrets, and almost every major plugin.

Conclusion

In this episode 22 we dissected the Helm plugin system: the architecture based on a simple directory with a plugin.yaml manifest and executables, six popular plugins you must master (helm-diff for diff-before-apply, helm-secrets for SOPS encryption, helm-unittest for testing, helm-git, helm-s3, and helm-push), managing plugins with helm plugin install/list/update/uninstall, and how to create a custom plugin — the manifest, scripts, the HELM_BIN and HELM_NAMESPACE environment variables, and distribution best practices.

The core takeaways:

  • Plugins extend Helm without changing its core — the core stays lean, the ecosystem grows.
  • helm diff before every upgrade is a discipline that saves production.
  • $HELM_BIN and $HELM_PLUGIN_DIR are the bridge between plugin and Helm — always use both.
  • platformCommand for multi-OS, hooks.install for setup, set -euo pipefail for reliability.

In the next episode, episode 23, we cover the Helm SDK & programmatic usage: no longer calling Helm as a CLI, but importing Helm as a Go library inside your own applications — the foundation of operators, platform engineering tools, and self-service portals. See you in episode 23!