Helm — Architecture#
Architectural style#
Layered CLI Tool with an Embeddable Library Core.
Helm is a single-binary CLI application whose architecture is deliberately stratified into three clean layers: a Cobra command tree (pkg/cmd), a business-logic action layer (pkg/action), and a set of infrastructure adapters (pkg/kube, pkg/storage, pkg/engine, pkg/registry). The separation between the CLI layer and the action layer is architecturally load-bearing: it allows pkg/action to be imported as a library by third parties (Flux, Argo CD, etc.) without dragging in any CLI concerns.
The action layer’s shared state is managed by a single Configuration struct that acts as a dependency injection container — injected into every action type at construction time. This is manual DI, not a framework. Key dependencies (Kubernetes client, storage backend, registry client) are swappable via interfaces, making the system testable and extensible.
Component diagram (textual)#
┌──────────────────────────────────────────────────────────────────────┐
│ cmd/helm (main) │
│ Sets kube.ManagedFieldsManager, calls pkg/cmd.NewRootCmd, runs │
└──────────────────┬───────────────────────────────────────────────────┘
│
┌──────────────────▼───────────────────────────────────────────────────┐
│ pkg/cmd (Cobra command tree) │
│ One file per subcommand: install.go, upgrade.go, rollback.go … │
│ Owns: CLI flags, arg validation, output formatting │
│ Creates action objects, sets fields from flags, calls Run() │
└──────────────────┬───────────────────────────────────────────────────┘
│ creates + calls
┌──────────────────▼───────────────────────────────────────────────────┐
│ pkg/action (business logic layer — the embeddable API) │
│ action.Configuration ← central DI container │
│ Install / Upgrade / Rollback / Uninstall / List / Status / … │
│ renderResources() ← template rendering + post-render pipeline │
└────┬────────────┬────────────┬────────────────┬───────────────────────┘
│ │ │ │
┌────▼───┐ ┌────▼──┐ ┌──────▼──────┐ ┌──────▼──────┐
│pkg/kube│ │pkg/ │ │pkg/storage │ │pkg/registry │
│ │ │engine │ │ │ │ │
│kube. │ │Go tpl │ │Storage │ │OCI client │
│Interface│ │+sprig │ │+ driver.* │ │(ORAS-based) │
│↕client-│ │render │ │secrets/ │ │push/pull/ │
│ go │ │ │ │configmaps/ │ │login/logout │
└────────┘ └───────┘ │memory/sql │ └─────────────┘
└─────────────┘
↑ all of the above sit on internal/* utility packagesCore components#
action.Configuration#
- Package:
helm.sh/helm/v4/pkg/action - File:
pkg/action/action.go - Responsibility: Central dependency injection container. Holds all shared dependencies for every action: Kubernetes REST client getter, release storage, kube client, OCI registry client, cluster capabilities cache, custom template functions, hook log output function, and a mutex for concurrent access.
- Key types:
Configurationstruct,ConfigurationOptionfunc type,RESTClientGetterinterface - Dependencies:
pkg/kube,pkg/storage,pkg/engine,pkg/registry,internal/logging - Init method:
Configuration.Init(getter, namespace, helmDriver)selects the storage driver (secrets/configmaps/memory/sql) via a switch statement and wires up the Kubernetes client.
action.Install / action.Upgrade / action.Rollback / …#
- Package:
helm.sh/helm/v4/pkg/action - Files:
install.go,upgrade.go,rollback.go,uninstall.go, etc. - Responsibility: Each action type encapsulates one Helm operation.
Installfields hold all configuration knobs for that operation (DryRunStrategy, WaitStrategy, Timeout, ReleaseName, Namespace, …). TheRun()method executes the full lifecycle: load chart → resolve dependencies → render templates → apply to Kubernetes → store release record. - Key types:
Install,Upgrade,Rollback,Uninstall,List,Status,Get,History,ChartPathOptions - Dependencies:
*Configuration(injected),pkg/chart,pkg/engine,pkg/kube,pkg/storage,pkg/downloader
pkg/kube.Interface#
- Package:
helm.sh/helm/v4/pkg/kube - File:
pkg/kube/interface.go - Responsibility: Abstracts all Kubernetes cluster operations needed by actions. Methods:
Get,Create,Update,Delete,Build(YAML → ResourceList),IsReachable,GetWaiter,GetPodList,OutputContainerLogsForPodList,BuildTable. - Key types:
Interface,Waiter,InterfaceWaitOptions,ResourceList,Result - Dependencies:
k8s.io/client-go,k8s.io/cli-runtime/pkg/resource - Implementations:
kube.Client(real cluster),kubefake.FailingKubeClient/kubefake.Client(test)
pkg/storage + pkg/storage/driver#
- Package:
helm.sh/helm/v4/pkg/storage,helm.sh/helm/v4/pkg/storage/driver - Files:
storage/storage.go,storage/driver/driver.go,driver/secrets.go,driver/configmaps.go,driver/memory.go,driver/sql.go - Responsibility: Versioned release record persistence.
Storageis a thin wrapper that adds MaxHistory enforcement on top of a pluggabledriver.Driver. The Driver interface is decomposed into small role interfaces (Creator,Updator,Deletor,Querier,Lister) that are composed intoDriver. - Key types:
storage.Storage,driver.Driverinterface,driver.Secrets,driver.ConfigMaps,driver.Memory,driver.SQL - Dependencies:
k8s.io/client-go(for secrets/configmaps drivers), PostgreSQL viajmoiron/sqlx+lib/pq(for SQL driver)
pkg/engine#
- Package:
helm.sh/helm/v4/pkg/engine - Responsibility: Renders a
chart.Chart’s templates against aValuesmap using Go’stext/templateaugmented with sprig and Helm-specific functions. TheEngine.Render()method returns amap[string]stringof filename → rendered YAML content. Supports an optional lookup function that calls the live Kubernetes API for dynamic values. - Key types:
Enginestruct (zero-value usable),Render(ch, values) map[string]string - Dependencies:
github.com/Masterminds/sprig/v3,k8s.io/client-go(for lookup function only)
pkg/cmd (Cobra command tree)#
- Package:
helm.sh/helm/v4/pkg/cmd - File:
pkg/cmd/root.go+ one file per subcommand - Responsibility: Defines the entire CLI surface via Cobra.
NewRootCmdwires up theConfiguration, registerscobra.OnInitialize(deferred driver initialization), adds all subcommands, and returns the rootcobra.Command. Each subcommand file creates and configures the corresponding action type. - Key types:
cobra.Command(fromgithub.com/spf13/cobra),CommandError(custom exit-code carrier) - Dependencies:
pkg/action,pkg/cli(EnvSettings),github.com/spf13/cobra
pkg/registry#
- Package:
helm.sh/helm/v4/pkg/registry - Responsibility: OCI registry client for pushing, pulling, logging in/out of container registries (used for chart distribution). Built on top of
oras.land/oras-goanddistribution/distributioncontent stores. - Key types:
Client,ClientOption(functional options) - Dependencies:
oras.land/oras-go,distribution/distribution
Data flow#
Tracing helm install myrelease ./mychart:
main()→pkg/cmd.NewRootCmd→ Cobra parses args, routes to install command handlercobra.OnInitializefires:actionConfig.Init(settings.RESTClientGetter(), namespace, helmDriver)— picks storage driver (e.g., Secrets), createskube.Clientbacked byclient-go- Install command handler (
pkg/cmd/install.go):- Creates
action.Install{cfg: actionConfig} - Sets all fields from CLI flags (namespace, timeout, dry-run mode, etc.)
- Calls
install.Run(args)
- Creates
action.Install.Run():- Resolves chart path (local dir, tarball, OCI ref, HTTP URL) via
pkg/getter - Loads chart via
pkg/chart/loader→chart.Chartstruct - Optionally updates dependencies via
pkg/downloader - Coalesces values (
--valuesfiles +--setflags) viapkg/cli/values - Calls
cfg.renderResources()→engine.Engine.Render()→ renderedmap[string]string - If post-renderer configured: merges YAML docs → calls
PostRenderer.Run()→ splits back - Sorts manifests (CRDs first, then by Kind) via
releaseutil.SortManifests - Pre-install hooks:
cfg.KubeClient.Create(hooks)→ wait for completion - Main resources:
cfg.KubeClient.Create(manifests)(client-side or server-side apply) - Wait for readiness if
--waitset:kube.Waiter.Wait(resources, timeout) - Post-install hooks
- Stores release record:
cfg.Releases.Create(release)→ serialized into Kubernetes Secret (or other backend)
- Resolves chart path (local dir, tarball, OCI ref, HTTP URL) via
- Release record written, NOTES.txt printed to stdout
Initialization / Bootstrap#
main()
└─ kube.ManagedFieldsManager = "helm"
└─ helmcmd.NewRootCmd(stdout, args, SetupLogging)
├─ action.NewConfiguration() // empty Configuration, no storage yet
├─ newRootCmdWithConfig(actionConfig, …)
│ ├─ parse flags early (for log level)
│ ├─ SetupLogging(debug) // configures log/slog
│ └─ registers all subcommands
└─ cobra.OnInitialize(func() {
actionConfig.Init( // deferred until command execution
settings.RESTClientGetter(),
settings.Namespace(),
os.Getenv("HELM_DRIVER"),
)
})
└─ cmd.Execute()Dependency injection pattern: Manual constructor injection via the Configuration struct. No DI framework (no Wire, Dig, or Fx). Each action type receives *Configuration in its constructor (NewInstall(cfg), etc.). ConfigurationOption functional options allow customizing the Configuration before use.
The HELM_DRIVER environment variable selects the storage driver at startup via a switch in Configuration.Init(). The Kubernetes REST client is lazy — constructed from genericclioptions.RESTClientGetter on first use, not at initialization time (lazyClient wrapper pattern in pkg/action/lazyclient.go).
Configuration#
Helm uses a three-tier configuration model:
Environment variables (
HELM_*): Primary configuration mechanism.pkg/cli.EnvSettingsreads ~20HELM_*env vars (cache path, config path, namespace, driver, max history, kubeconfig, TLS settings, etc.) and exposes them as fields on a settings struct. These map to XDG-compliant filesystem paths viapkg/helmpath.Persistent CLI flags: Cobra persistent flags on the root command shadow env vars (e.g.,
--namespace,--debug,--kubeconfig,--kube-context).settings.AddFlags(flags)registers them all at once.Kubeconfig / cluster access: The
genericclioptions.RESTClientGetterfromk8s.io/cli-runtimereads the standard kubeconfig chain (KUBECONFIGenv,~/.kube/config, in-cluster service account). Helm does not have its own kubeconfig format.
No Viper. Configuration is simple and explicit: env vars → EnvSettings struct → passed into Configuration.Init().
Key design decisions#
pkg/actionas the stable embeddable API. The separation of CLI (pkg/cmd) from business logic (pkg/action) is the most intentional architectural choice. The package doc explicitly states: “This is a library for calling top-level Helm actions.” Projects like Flux and Argo CD importpkg/actiondirectly. This imposes discipline:pkg/actionmust not importpkg/cmd, and its public API must be treated as a library contract.Configurationas a manual DI container. Rather than using a DI framework, Helm passes a single*Configurationstruct into every action. This is explicit, debuggable, and easy to understand. TheConfigurationOptionfunctional-option pattern allows incremental customization. The trade-off is that adding a new shared dependency requires modifyingConfigurationand updating all callers.Storage driver strategy pattern.
driver.Driveris an interface with four implementations (Secrets, ConfigMaps, Memory, SQL). The selection happens at startup via a switch onHELM_DRIVER. The SQL backend is new in v4 (PostgreSQL viajmoiron/sqlx). This cleanly isolates release persistence from all action logic — actions callcfg.Releases.Get/Create/Update, never touching Kubernetes directly for storage.kube.Interfacefor full testability. All Kubernetes cluster operations go throughkube.Interface, which has a fake implementation inpkg/kube/fake/. This means action tests can run entirely without a cluster. The real implementation (kube.Client) drivesclient-gowith server-side apply support viafluxcd/cli-utils.Post-renderer extensibility via pipeline injection. The
postrenderer.PostRendererinterface (Run(io.Reader) (*bytes.Buffer, error)) sits between template rendering and Kubernetes apply. Helm’srenderResources()serializes all rendered YAML into a merged stream, passes it through the post-renderer, then reconstructs the per-file map via filename annotations. This allows arbitrary YAML transformation (Kustomize, custom scripts) without modifying Helm internals. In v4, a WASM-based post-renderer is being introduced alongside the existing exec-based one.