tomato·bazeldocs v0 · latest
Docs/Reference/Modules/rules_helm

rules_helm

Chart-as-a-module: build, lint, and publish Helm charts in Bazel (the Helm analog of rules_oci).

Latest0.2.0
Versions2
CategoryBazel rules
MaintainersMatt Marshall
Registryhttps://registry.tbzl.dev/modules/rules_helm/
Sourcegithub.com/tomato-bazel/rules_helm
MODULE.bazelstarlark
bazel_dep(name = "rules_helm", version = "0.2.0")

View source & releases on GitHub ↗

Chart-as-a-module: build, lint, and publish Helm charts in Bazel — the Helm analog of how this org already does container images (rules_oci) and protos.

Helm itself is fetched hermetically (a pinned per-platform binary from get.helm.sh), so bazel build-ing a chart needs nothing installed on the host.

helm_template

Render a chart someone else published into manifests you can read, review, diff and gate:

load("@rules_helm//helm:defs.bzl", "helm_template")

helm_template(
    name = "argocd_manifests",
    chart = "@argo_cd_chart//file",   # http_file, pinned by sha256
    release_name = "argocd",
    namespace = "argocd",
    values = ["values.yaml"],
)

helm install renders and applies in one motion, so what actually reached the cluster is knowable only afterwards, by asking the cluster. Rendering to a file makes it reviewable before anything moves, byte-stable across rebuilds, and diffable against a live cluster.

The action has no network, so chart must be a self-contained .tgz — a chart with unvendored subcharts fails here rather than silently fetching them. Pin it with http_file + sha256 so the render is a pure function of committed inputs.

Rules

RuleKindWhat it does
helm_chartbuild actionPackages a chart directory into <name>.tgz.
helm_lintbuild actionhelm lint (fails the build); cacheable marker output.
helm_pushbazel runhelm push <tgz> oci://<repository>.

Use

MODULE.bazel:

bazel_dep(name = "rules_helm", version = "0.1.0")

BUILD.bazel next to a chart:

load("@rules_helm//helm:defs.bzl", "helm_chart", "helm_lint", "helm_push")

_SRCS = ["values.yaml"] + glob(["templates/**", ".helmignore"])

helm_chart(
    name = "chart",
    chart_yaml = "Chart.yaml",
    srcs = _SRCS,
    version = "1.2.3",        # optional; defaults to Chart.yaml
    app_version = "1.2.3",    # optional; defaults to Chart.yaml
)

helm_lint(name = "lint", chart_yaml = "Chart.yaml", srcs = _SRCS)

helm_push(name = "push", chart = ":chart", repository = "ghcr.io/fastverk/charts")
bazel build //path/to:chart        # -> bazel-bin/path/to/chart.tgz
bazel build //path/to:lint         # fails if the chart doesn't lint
helm registry login ghcr.io ...    # auth once (CI uses GITHUB_TOKEN)
bazel run   //path/to:push         # publishes the .tgz

srcs must list every file helm should package — the action runs in a sandbox containing only the declared inputs. helm_chart is for leaf charts; a chart with subchart dependencies must have its charts/ vendored (helm dependency build) and the vendored archives passed in srcs.

Bumping helm

Two edits in helm/repositories.bzl: HELM_VERSION and the four HELM_SHA256 lines. Refresh a checksum with:

curl -fsSL https://get.helm.sh/helm-v<ver>-<platform>.tar.gz.sha256sum

This v0.1.0 downloads only the host platform’s binary (chart packaging always runs host-native). A future revision can graduate this to a full multi-platform Bazel toolchain for exec-platform selection (RBE).

Publishing this module

Tag a release and run the registry tool (same flow as the other fastverk rules_* modules):

git tag v0.1.0 && git push origin v0.1.0
# in repos/bazel-registry:
bazel run //tools/rels -- release --repo fastverk/rules_helm --version 0.1.0

Usage#

Real usage, taken from the module’s examples/.

examples/smoke/BUILD.bazel

load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
load("@rules_helm//helm:defs.bzl", "helm_chart", "helm_lint", "helm_push", "helm_template")

# All chart files the action needs (the sandbox sees only declared inputs).
_SRCS = [
    "chart/values.yaml",
    "chart/templates/deployment.yaml",
]

helm_chart(
    name = "smoke",
    chart_yaml = "chart/Chart.yaml",
    srcs = _SRCS,
    version = "0.1.0",
    app_version = "0.0.0",
)

helm_lint(
    name = "lint",
    chart_yaml = "chart/Chart.yaml",
    srcs = _SRCS,
)

helm_push(
    name = "push",
    chart = ":smoke",
    repository = "ghcr.io/fastverk/charts",
)

# ── helm_template ────────────────────────────────────────────────────────────
# Round-trips the chart this package already builds: helm_chart packages it, then
# helm_template renders it back to manifests. Self-contained, so the test proves
# the rule works without depending on any third-party chart staying published.
helm_template(
    name = "smoke_rendered",
    chart = ":smoke",
    namespace = "smoke-ns",
    release_name = "smoke-release",
    values = ["chart/values.yaml"],
)

# The whole point of rendering to a file is that the output is reviewable and
# STABLE. If a helm upgrade or a rule change perturbs it, that must surface as a
# failing test rather than as a silent difference in what reaches a cluster.
diff_test(
    name = "smoke_rendered_stable",
    file1 = "expected_smoke_rendered.yaml",
    file2 = ":smoke_rendered",
)

Conformance#

No gate findings. 8 contested atoms. See how gating works or the full report.

Contested atoms

Third-party modules where this module resolves a different version than others do. Not a violation of anything this module did — it is the actionable form of a registry-level convergence finding, and the sentence a maintainer can act on.

AtomResolved hereElsewhere
apple_support 1.24.2 2.2.0 ×1
bazel_skylib 1.8.2 1.9.0 ×2
nlohmann_json 3.6.1 3.12.0.bcr.1 ×1
protobuf 33.4 34.0.bcr.1 ×2
rules_jvm_external 6.7 6.8 ×4
rules_python 1.7.0 2.0.1 ×1
rules_swift 3.1.2 3.6.1 ×1
upb 0.0.0-20220923-a547704 0.0.0-20230516-61a97ef ×1

Dependencies#

rules_helm in the registry graph — what it depends on (left) and what depends on it (right).

Depends on

platforms1.0.0bazel_skylib1.8.2rules_shell0.6.1dev

Used by (1 in the registry)

Versions#

2 published versions, newest first. Each resolves to an immutable, integrity-checked archive.

VersionIntegrity (sha256)Source archive
0.2.0 latest nfhDMEqg4nHz/xF+… tag archive ↗
0.1.0 cpmWWy6NMci5bDFJ… tag archive ↗

Changelog#

0.2.0 — helm_template

  • helm_template — render a packaged chart to a single manifest file, as a build action. helm_chart packages a chart WE author; this renders a chart SOMEONE ELSE published into something we can read, review, diff and gate. Opposite directions, and the second is what a consumer of a third-party chart actually needs.
  • The point is that the output becomes an artifact rather than an event. helm install renders and applies in one motion, so what reached the cluster is knowable only afterwards by asking the cluster. Rendering to a file makes it reviewable BEFORE anything moves, byte-stable across rebuilds, and diffable against a live cluster with rules_k8s’s k8s_diff.
  • Fully offline: the action has no network, so chart must be a self-contained .tgz. A chart with unvendored subchart dependencies fails loudly here rather than silently fetching them. Pin it with http_file + sha256 so the render is a pure function of committed inputs.
  • set uses --set-string deliberately. Plain --set applies YAML type inference, so an image tag like 1.34 renders as a float and true becomes a bool. A value that must keep its type belongs in a values file.
  • Attrs: chart, values, set, release_name, namespace, include_crds, kube_version, api_versions.
  • Smoke test round-trips the example chart through helm_chart then helm_template and diff-tests the result, so a helm upgrade that perturbs the output fails a test rather than silently changing what would reach a cluster. The example template now reads .Release.Name/.Release.Namespace so the golden file actually proves those flags are plumbed — it did not before.

All notable changes to this module are documented here; this project adheres to Keep a Changelog and SemVer.

[Unreleased]

← All modules