Helm¶
Who this page is for: in plain English, Helm packages a set of Kubernetes manifests into a versioned, configurable bundle called a chart, and tracks what you've installed so you can upgrade and roll back. This page is production depth, aimed at readers already comfortable with Pods, Services, and Deployments. New to Kubernetes? Start with the Beginner track.
Helm is the standard package manager for Kubernetes. It wraps a set of manifests into a versioned, configurable artifact called a chart, and tracks deployed instances - called releases - so you can upgrade, diff, and roll back with a single command.
Without Helm, environment-specific configuration lives in duplicated YAML or fragile kustomize overlays. With Helm, you parameterize once and deploy anywhere.
A note on versions: Helm 4.0.0 shipped in November 2025, the first major version bump in six years, and is the actively developed release line as of 2026. Helm 3 is in maintenance: the project announced a bug-fix window followed by a longer security-patch-only window after the Helm 4 GA, and the exact end dates are published on Helm's version support policy and on the Helm blog - check those rather than trusting a date written here. Plenty of production clusters are still on Helm 3, and most of what's below applies to both lines. Helm 4's headline changes: Server-Side Apply is now used by default for new installs, the plugin system was redesigned (Wasm-based plugins, OCI plugin installation), and post-renderers were folded into that plugin system - see the callout in the post-rendering section below, since it changes a command shown on this page. Chart.yaml's apiVersion: v2 format is unchanged and remains the one to use; an experimental v3 chart API exists behind a feature flag but isn't stable yet.
How a release works¶
flowchart LR
Chart["Chart\n(templates + defaults)"] --> Render["helm template\n(render with values)"]
Values["values.yaml\n+ overrides"] --> Render
Render --> Release["Release in cluster\n(tracked in Secrets)"]
Release --> Upgrade["helm upgrade\n(diff + apply)"]
Upgrade --> History["Release history\n(rollback target)"]
Helm stores each release's rendered manifest and metadata as a Secret in the target namespace. That history is what makes helm rollback work - it renders the previous revision's manifest and re-applies it.
Chart anatomy¶
my-chart/
├── Chart.yaml # metadata: name, version, appVersion, dependencies
├── values.yaml # default values for all templates
├── charts/ # vendored sub-charts (from helm dependency build)
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── _helpers.tpl # named templates (partials) -- not rendered directly
│ └── NOTES.txt # post-install user-facing instructions
└── .helmignore
Chart.yaml fields that matter¶
apiVersion: v2
name: my-app
version: 1.4.2 # chart version -- bump this on every chart change
appVersion: "2.0.1" # application version -- informational only
description: My application chart
type: application # or "library" for reusable partials
dependencies:
- name: postgresql
version: "12.x.x"
repository: "oci://registry-1.docker.io/bitnamicharts"
condition: postgresql.enabled
Templating with Sprig¶
Helm templates use Go's text/template engine augmented with the Sprig function library.
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "my-app.selectorLabels" . | nindent 6 }}
template:
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.env }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
Named templates (_helpers.tpl)¶
{{/*
Expand the name of the chart.
*/}}
{{- define "my-app.fullname" -}}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels applied to every resource.
*/}}
{{- define "my-app.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
Prefer include over template - include returns a string you can pipe through nindent, quote, and other functions.
Advanced templating: tpl, lookup, and whitespace control¶
Three patterns separate charts that merely work from charts that hold up under real production use.
tpl - render a value as a template. Sometimes a value itself contains template syntax - a Helm value that a user wants to interpolate with other values at render time (a common case: an annotation or a log-format string that references .Release.Name):
# templates/deployment.yaml
metadata:
annotations:
{{- tpl (toYaml .Values.podAnnotations) . | nindent 4 }}
Without tpl, that string is inserted literally - {{ .Release.Revision }} shows up verbatim in the manifest instead of being evaluated. tpl takes a string and the current context (.) and re-runs it through the template engine. It's the mechanism that lets umbrella charts and library charts expose "templatable" values to consumers who don't control the chart source.
lookup - read live cluster state during templating. lookup queries the Kubernetes API for existing objects:
{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace "db-credentials" }}
{{- if not $existingSecret }}
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
data:
password: {{ randAlphaNum 32 | b64enc }}
{{- end }}
This is how charts avoid regenerating a random password on every upgrade - check if the Secret already exists, and only generate one if it doesn't. The catch: lookup only works when Helm is actually talking to a live cluster. helm template renders offline with no API connection, so lookup silently returns an empty result there - your "does it exist" check will always say no. helm install/upgrade work correctly because they run against the real cluster. There is no flag that gives helm template cluster access, so if you need lookup to resolve during a render (CI preview, GitOps diff), use helm install --dry-run=server or helm upgrade --dry-run=server instead: those run the full render against a real API server connection and print the manifests without persisting a release. Don't build hard dependencies on lookup for anything that must render correctly offline in CI.
Whitespace control ({{- / -}}) pitfalls. The hyphen chomps adjacent whitespace, including newlines - {{- eats everything backward to the previous non-whitespace character, -}} eats everything forward. Overusing it collapses YAML onto one line and breaks indentation; underusing it leaves blank lines that are usually harmless but occasionally invalid (a blank line inside a multi-line scalar, for instance). The most common failure mode is chomping across a line you needed:
vs.
The second form chomps the newline after if, which pulls ingress: onto the same line as the (invisible) if output - fine here since the if renders nothing, but a frequent source of "why is my YAML malformed" bugs when the block being conditioned on isn't empty. When debugging whitespace issues, render with helm template --debug and look at the raw output rather than guessing from the template source.
Values architecture¶
A production values strategy has three layers:
values.yaml ← chart defaults (committed, no secrets)
values-prod.yaml ← environment overrides (committed)
values-secrets.yaml ← sensitive overrides (from Vault/SOPS, not committed)
helm upgrade my-app ./my-chart \
-f values-prod.yaml \
-f values-secrets.yaml \
--set image.tag=v2.3.1
Values from rightmost -f file win. --set wins over all files.
Secrets in values¶
Never commit plaintext secrets. Common patterns:
- External Secrets Operator: Helm creates the ExternalSecret CRD, which pulls from Vault/SSM at runtime.
- SOPS + helm-secrets plugin:
helm secrets upgrade ...decrypts on the fly. - Sealed Secrets: encrypt with the cluster's public key; commit the SealedSecret manifest.
Install and upgrade workflow¶
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install my-app ./my-chart -n platform --create-namespace -f values-prod.yaml
# Before upgrading: render and diff
helm diff upgrade my-app ./my-chart -n platform -f values-prod.yaml
helm upgrade my-app ./my-chart -n platform -f values-prod.yaml --atomic --cleanup-on-fail
# Verify, then rollback if needed
helm status my-app -n platform
helm history my-app -n platform
helm rollback my-app 3 -n platform
--atomic rolls back automatically if the upgrade fails. --cleanup-on-fail deletes newly created resources on failure. On Helm 4, --atomic was renamed --rollback-on-failure; the old name still works on helm upgrade with a deprecation warning, so the examples above still run, but new automation should move to the new flag name.
Hooks¶
Hooks are Jobs (or Pods) that run at defined release lifecycle points.
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
annotations:
"helm.sh/hook": pre-upgrade,pre-install
"helm.sh/hook-weight": "-5" # lower runs first
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["python", "manage.py", "migrate"]
| Hook | Use case |
|---|---|
pre-install / pre-upgrade |
database migrations, CRD registration |
post-install / post-upgrade |
smoke tests, cache warm-up |
pre-delete |
drain connections, archive data |
test |
helm test triggered checks |
hook-delete-policy: before-hook-creation prevents old hook Jobs from blocking subsequent upgrades. hook-succeeded cleans up passing Jobs automatically.
Post-rendering: patching charts you don't control¶
Sometimes you consume a third-party chart that doesn't expose a value for something you need to change - a missing securityContext, a hardcoded automountServiceAccountToken, a sidecar annotation your platform requires on every pod. Forking the chart is high-maintenance; a post-renderer lets you patch the rendered output without touching the chart source.
On Helm 3, the post-renderer is any executable that reads the fully rendered manifest stream on stdin and writes the patched manifest stream on stdout. In practice this is almost always Kustomize:
#!/usr/bin/env sh
# patch.sh
exec kustomize build --load-restrictor=LoadRestrictionsNone /path/to/kustomization-dir
# kustomization.yaml (the post-render directory)
resources:
- stdin.yaml # placeholder; kustomize reads actual input piped by Helm
patches:
- target:
kind: Deployment
name: third-party-app
patch: |-
- op: add
path: /spec/template/spec/securityContext
value:
runAsNonRoot: true
There's a subtlety worth internalizing: Helm's --post-renderer operates in the opposite direction from Kustomize's helmCharts: generator. helmCharts: is Kustomize calling out to Helm to expand a chart as one input among its other resources - Kustomize is in the driver's seat. --post-renderer is Helm calling out to an external process (frequently Kustomize) to touch up its own output before applying - Helm is in the driver's seat. Pick based on which tool owns the rest of your pipeline: if your GitOps repo is fundamentally a Kustomize tree, prefer helmCharts:; if it's fundamentally a Helm release with one stubborn upstream chart, prefer --post-renderer. On Helm 3, post-renderers also compose with --post-renderer-args for passing arguments to the renderer script (check helm install --help on your version - the flag was added partway through the 3.x line), and they run on every install, upgrade, and template invocation, so keep them deterministic and side-effect free.
Helm 4: post-renderers are plugins, not bare executables. This is a breaking change from Helm 3, not a deprecation with a grace period - as of Helm 4, helm install --post-renderer ./patch.sh fails with plugin: ... not found; you can no longer point --post-renderer straight at a script or binary. The script itself doesn't need to change, but it has to be wrapped as a Helm plugin (a directory with a plugin.yaml declaring a postrenderer/v1 plugin type) and installed with helm plugin install, then referenced by plugin name instead of path:
# Helm 4
helm plugin install ./my-post-renderer-plugin
helm install my-app third-party/chart --post-renderer my-post-renderer-plugin
If you maintain post-render scripts (including the Kustomize-as-post-renderer pattern below) and need to support both Helm versions during a migration window, expect to ship two invocation paths: the bare-executable form for Helm 3, the plugin-wrapped form for Helm 4.
Helm test¶
Ship test Jobs inside your chart to verify a deployed release:
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "my-app.fullname" . }}-test-connection"
annotations:
"helm.sh/hook": test
spec:
restartPolicy: Never
containers:
- name: wget
image: busybox
command: ['wget', '--spider', 'http://{{ include "my-app.fullname" . }}:{{ .Values.service.port }}/health']
Chart testing and linting in CI¶
helm test verifies a deployed release. Before you ever deploy, a layered CI pipeline should catch problems in the chart itself:
helm lint → static analysis: required fields, YAML validity, best-practice warnings
helm template → does it render at all, for every values file you ship
chart-testing (ct) → lint + install + test against a real (usually kind) cluster, per changed chart
unittest plugin → assert on rendered output without a cluster at all
helm lint catches the basics - missing Chart.yaml fields, invalid YAML, values referenced but never defaulted:
--strict promotes warnings (like an image tag that looks like latest) to errors, which is what you want gating a merge.
chart-testing (ct), the tool the Helm project itself uses for helm/charts style monorepos, wraps lint + a real helm install against a scratch cluster + helm test, and is smart about only testing charts that changed in a PR:
ct lint --config ct.yaml
ct install --config ct.yaml # spins up kind, installs each changed chart, runs helm test
This is the closest thing to an integration test for a chart - it catches problems helm lint structurally cannot, like a Job hook that never reaches Completed or a Service selector that doesn't match any Pod.
The helm-unittest plugin takes the opposite approach: no cluster, pure rendered-output assertions, fast enough to run on every commit. Tests live alongside the chart and assert against helm template output:
# tests/deployment_test.yaml
suite: test deployment
templates:
- deployment.yaml
tests:
- it: should set replica count from values
set:
replicaCount: 3
asserts:
- equal:
path: spec.replicas
value: 3
- it: should not mount a volume when persistence is disabled
set:
persistence.enabled: false
asserts:
- notExists:
path: spec.template.spec.volumes
Golden-file snapshot testing is the same idea taken further: render the chart with a fixed set of values, commit the output, and fail CI if a future change produces a diff you didn't review and re-approve:
helm template my-app ./my-chart -f values-prod.yaml > testdata/golden/prod.yaml
# in CI:
helm template my-app ./my-chart -f values-prod.yaml | diff testdata/golden/prod.yaml -
Golden files catch the class of bug unit assertions miss - an unrelated template change that quietly reformats indentation or drops a label across the entire rendered manifest, not just the field a targeted assertion checks. The tradeoff is churn: every intentional change requires regenerating and re-committing the golden file, so reserve this for charts stable and important enough to be worth the review overhead (platform base charts, not every microservice chart).
A reasonable CI gate order: helm lint --strict → helm unittest → golden-file diff → ct install on changed charts only (it's the slowest, so don't run it on every chart in a monorepo, just the ones touched by the PR).
OCI registry support and chart repository mechanics¶
Charts can be stored in OCI registries (Docker Hub, ECR, Artifact Registry, Harbor):
helm package ./my-chart
helm push my-chart-1.4.2.tgz oci://registry.example.com/charts
helm pull oci://registry.example.com/charts/my-chart --version 1.4.2
helm install my-app oci://registry.example.com/charts/my-chart --version 1.4.2
OCI registries don't need helm repo add. Use your existing container registry for chart storage and your existing registry credentials - no separate chart-hosting infrastructure, and the same image-pull auth (IRSA, Workload Identity, imagePullSecrets) that already governs your containers governs your charts.
The classic repo model, for comparison¶
The original (still widely used) distribution mechanism is an HTTP index, not a registry. helm repo add points at a URL serving an index.yaml:
# index.yaml (generated, not hand-written)
apiVersion: v1
entries:
my-app:
- version: 1.4.2
appVersion: "2.0.1"
created: "2026-01-15T10:00:00Z"
urls:
- https://charts.example.com/my-app-1.4.2.tgz
digest: sha256:...
Publishing means packaging, regenerating the index, and re-hosting it:
helm package ./my-chart -d ./repo
helm repo index ./repo --url https://charts.example.com --merge ./repo/index.yaml
# upload ./repo/*.tgz and the updated index.yaml to your static host
helm repo index --merge folds new entries into an existing index rather than overwriting history - forgetting --merge is a classic way to accidentally delete every older chart version from the index in one publish.
Why the ecosystem is moving to OCI¶
The index.yaml model has structural weaknesses OCI doesn't share: it's a single flat file every client re-downloads and re-parses on helm repo update, it has no native content-addressability or layer dedup, and it requires bespoke static hosting plus a separate signing story from your container images. OCI gives charts the same distribution model as images - content-addressed blobs, registry-native auth, cosign/sigstore compatibility, and one piece of infrastructure (the registry) for both artifacts. Most major chart publishers (Bitnami, the Kubernetes project's own charts, most CNCF projects) now publish to OCI as the primary channel, with classic HTTP repos kept around for compatibility. If you're standing up chart distribution today, default to OCI and only stand up an index.yaml repo if you have consumers who can't yet pull OCI artifacts.
Library charts¶
A library chart (type: library in Chart.yaml) exports only named templates - no manifests rendered directly. Use it to share _helpers.tpl patterns across charts in a monorepo.
# Chart.yaml for an application chart
dependencies:
- name: my-lib
version: "0.1.0"
repository: "oci://registry.example.com/charts"
Dependency management¶
helm dependency update ./my-chart # resolve and download to charts/
helm dependency build ./my-chart # rebuild from Chart.lock (CI usage)
Pin dependencies to exact versions or SemVer ranges in Chart.yaml. Use condition: to make sub-charts opt-in via values.
Umbrella charts, sub-chart addressing, and global values¶
An umbrella (parent) chart has no templates of its own - or only a handful - and exists mainly to compose several sub-charts into one installable, versioned unit. This is how many platform teams ship "install our whole stack" as a single helm install.
# Chart.yaml
apiVersion: v2
name: platform
version: 3.2.0
dependencies:
- name: api
version: "1.4.x"
repository: "oci://registry.example.com/charts"
- name: worker
version: "1.1.x"
repository: "oci://registry.example.com/charts"
- name: redis
version: "18.x.x"
repository: "oci://registry-1.docker.io/bitnamicharts"
condition: redis.enabled
The parent's values.yaml addresses each sub-chart by name as a top-level key, and every value under that key is passed down as if it were the sub-chart's own values.yaml:
# values.yaml (parent)
api:
replicaCount: 3
image:
tag: "2.0.1"
worker:
replicaCount: 5
redis:
enabled: true
auth:
enabled: true
Override a nested sub-chart value from the CLI with dotted addressing:
global: is the escape hatch for values every sub-chart (and the parent) needs to agree on - an image pull secret name, a common label, a cluster region. Anything under global: in the parent's values is merged into .Values.global for every sub-chart automatically, without the parent needing to repeat it under each sub-chart's key:
# inside a sub-chart's template
image: "{{ .Values.global.imageRegistry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}"
Sub-charts must explicitly reference .Values.global.X in their own templates for this to have any effect - global: doesn't silently override sub-chart-local values with the same name, it's a separate namespace they opt into reading. A common mistake is assuming a top-level parent value like imageRegistry: (not nested under global:) will reach into a sub-chart; it won't, because sub-charts only ever see their own namespaced values plus whatever landed under .Values.global.
Debugging a failed release: a playbook¶
A stuck or failed release is where Helm's abstractions leak the most. Work through this in order.
1. What did Helm think it was applying?
This is the rendered YAML for a specific revision, reconstructed from the release Secret - not necessarily what's live now. helm get manifest with no --revision shows the current recorded revision's manifest, which after a failed non-atomic upgrade can differ significantly from what's actually running in the cluster.
2. What's actually live?
kubectl get all -n platform -l app.kubernetes.io/instance=my-app
kubectl diff -f <(helm get manifest my-app -n platform)
kubectl diff against the recorded manifest is the fastest way to see drift - resources someone hand-edited, or resources the last upgrade never got to.
3. Orphaned resources after a failed atomic rollback. --atomic rolls back to the previous revision on failure, but rollback is itself a helm upgrade under the hood, and it can also fail or partially apply - particularly for resources that Helm considers immutable-on-update (a Job's spec.selector, some StatefulSet fields) where the rollback's own patch is rejected by the API server. The result: resources created by the failed forward upgrade that the rollback couldn't clean up, sitting orphaned in the namespace, not referenced by the release Helm now considers current. helm get manifest won't show them because they're not part of any revision Helm currently tracks as active. Find them the unglamorous way - diff kubectl get all -n platform against helm get manifest, or look for resources with the release's labels but a helm.sh/chart annotation value that doesn't match helm history's current entry.
4. What does --wait actually wait for? --wait (implied by --atomic) blocks until Helm considers every resource in the release "ready," and readiness is kind-specific and easy to misjudge the timeout for:
| Kind | "Ready" means |
|---|---|
Deployment |
status.availableReplicas == desired, per minReadySeconds |
StatefulSet |
all replicas at currentRevision == updateRevision, ready |
Job |
status.succeeded reaches completions |
Service (type LoadBalancer) |
status.loadBalancer.ingress is populated |
PersistentVolumeClaim |
phase is Bound |
--timeout (default 5m) applies to the whole operation, not per-resource - a chart with a slow-provisioning LoadBalancer Service and a slow-starting StatefulSet share one clock. A release that fails with "context deadline exceeded" isn't necessarily broken; it may just need --timeout 10m. Don't reflexively raise the timeout without checking why something was slow first (an unschedulable pod will never become ready no matter how long you wait, and a longer timeout just delays finding that out).
5. History is your audit log. helm history my-app -n platform shows every revision with its status (deployed, failed, superseded, rolled-back) - read it before assuming the current state matches the latest revision number; a failed revision still increments the counter.
Use the helm-diff plugin in CI to produce a diff between the current release and what helm upgrade would apply. This is the best way to catch unintended template changes before deployment.
helm template my-app ./my-chart -f values-prod.yaml # render locally
helm template my-app ./my-chart -f values-prod.yaml --debug # show computed values
helm get manifest my-app -n platform # what's live in cluster
helm get values my-app -n platform # what values are active
Chart provenance, signing, and supply-chain hygiene¶
Charts pulled from public repositories are executable configuration for your cluster - a malicious or compromised chart can create arbitrary resources, including ones with cluster-admin RBAC. Treat chart supply chain with the same seriousness as image supply chain.
Signing a chart you publish:
helm package ./my-chart --sign --key 'my-signing-key' --keyring ~/.gnupg/secring.gpg
# produces my-chart-1.4.2.tgz and my-chart-1.4.2.tgz.prov
The .prov (provenance) file contains a signed hash of the chart archive and a copy of Chart.yaml. Anyone installing the chart can verify it wasn't tampered with after signing:
helm verify my-chart-1.4.2.tgz --keyring ~/.gnupg/pubring.gpg
helm install my-app ./my-chart-1.4.2.tgz --verify --keyring ~/.gnupg/pubring.gpg
For OCI-distributed charts, the modern equivalent is cosign sign/cosign verify against the chart's OCI artifact, which fits the same keyless/Sigstore-based signing flow increasingly used for container images - one signing story for both artifact types rather than Helm's GPG-based provenance and a separate image-signing pipeline.
Supply-chain concerns to actually check, not just sign for:
- Pin exact chart versions.
version: "12.x.x"in a dependency is convenient but means your nexthelm dependency updatecan silently pull in a new minor version with different defaults, new RBAC, or a compromised release. Pin exact versions for anything security-sensitive and bump deliberately. - Review before first install, not after.
helm templatea chart from an unfamiliar repository and read the rendered RBAC,hostNetwork/hostPathusage, and any hook Jobs before installing - hooks in particular run with whatever ServiceAccount the release uses and are easy to overlook in a large chart. - Mirror charts you depend on critically. Public chart repositories go away, get renamed, or get compromised. For anything load-bearing, mirror the chart (and its OCI digest, not just its tag) into a registry you control rather than depending on
helm dependency updatereaching the internet at deploy time. Chart.lockis your reproducibility guarantee, treat it like a lockfile.helm dependency buildinstalls exactly whatChart.lockrecords (including digests);helm dependency updaterecomputes it. CommitChart.lockand usebuildin CI, notupdate, the same way you'd usenpm ciovernpm install.
Common production mistakes¶
| Mistake | Fix |
|---|---|
Bumping appVersion without bumping version |
Always increment version when chart content changes |
--set in CI without a diff step |
Use helm diff to catch unexpected changes |
Forgetting --atomic on upgrade |
Cluster can end up with partial upgrade if pods fail |
Using latest as the image tag |
Use explicit digest or immutable tag; appVersion should match |
| Storing release secrets in default namespace | Deploy to the namespace you intend; secrets stay there |
Assuming lookup works in helm template |
It never does - helm template has no cluster connection. Use helm install/upgrade --dry-run=server to render with lookups, and don't depend on lookup for offline CI rendering |
helm dependency update in CI instead of build |
update re-resolves ranges and can silently pull a new sub-chart version; use build against a committed Chart.lock |
No helm lint/unittest gate before merge |
Broken templates reach helm upgrade instead of failing fast in CI |
Unpinned chart version ranges (12.x.x) on security-sensitive dependencies |
Pin exact versions; bump deliberately after review |
Source Links¶
- Helm documentation
- helm/helm on GitHub and its releases
- Helm version support policy and version skew
- Helm blog (release announcements)
- Chart template developer's guide (including
lookup) - Chart hooks
- Helm plugins
- Registries: charts in OCI
helmCLI reference- CNCF project page: Helm
Related Concepts¶
- Kustomize - the template-free alternative (and companion)
- Operators and CRDs
- ConfigMaps and Secrets
- Argo CD
- OPA Gatekeeper - policy-checking rendered chart output before it's applied
- Kyverno - alternative policy engine for the same purpose