How to Write and Lint Kubernetes Helm Charts
A Helm chart turns fixed Kubernetes manifests into templates driven by values.yaml, so one chart deploys any image, tag, or replica count. This project builds a minimal chart from three files and validates it with helm lint (expecting 0 charts failed) and helm template - no helm install required.
Why Helm charts, and what a chart actually is
A raw Kubernetes Deployment hard-codes the image, tag, and replica count. Deploy the
same app to staging and prod and you end up copy-pasting near-identical YAML. A Helm
chart fixes that: it is a directory of templates plus a values.yaml of inputs, and
Helm renders them into real manifests at deploy time. Change one value, get a new
environment - no forked YAML.
A minimal chart needs exactly three files:
chart/Chart.yaml- chart metadatachart/values.yaml- the default inputschart/templates/deployment.yaml- a manifest that reads from those values
This project authors and validates that chart. It intentionally stops before
helm install - helm lint and helm template prove the chart is correct without
touching a cluster.
Step 1 - Chart.yaml metadata
Chart.yaml identifies the chart. apiVersion: v2 is required for Helm 3; name
and version are what helm lint checks for.
apiVersion: v2
name: web
description: Web deployment
type: application
version: 0.1.0
appVersion: "1.0"
Note appVersion is quoted - "1.0" would otherwise parse as a float and drop the
trailing zero.
Step 2 - values.yaml inputs
These are the defaults every template can reference. Override any of them at render
time with --set.
image: nginx
tag: "1.27"
replicaCount: 2
Step 3 - the templated Deployment
In chart/templates/deployment.yaml, replace the hard-coded fields with Helm's
{{ .Values.* }} syntax. {{ .Release.Name }} is a built-in Helm gives you at
render time - it is the release name you pass to helm template/helm install.
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-web
labels:
app: {{ .Release.Name }}-web
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Release.Name }}-web
template:
metadata:
labels:
app: {{ .Release.Name }}-web
spec:
containers:
- name: web
image: "{{ .Values.image }}:{{ .Values.tag }}"
Quote the image line so the rendered value comes out as a string
(image: "nginx:1.27"), not a bare token.
Step 4 - lint and render
helm lint checks the chart structure - required Chart.yaml fields, valid template
syntax, YAML that parses:
helm lint chart/
# 1 chart(s) linted, 0 chart(s) failed
helm template renders the chart to stdout so you can eyeball the actual manifest
Helm would apply. This never contacts a cluster:
helm template web chart/
The rendered output should contain image: "nginx:1.27" and replicas: 2. To prove
the values are wired correctly, override one and confirm the change:
helm template web chart/ --set replicaCount=5 # renders replicas: 5
If --set replicaCount=5 still shows replicas: 2, the template is not reading from
.Values.replicaCount - it has the number hard-coded. That is the single most common
mistake when writing a first chart.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Structuring a Helm chart with Chart.yaml, values.yaml, and a templates/ directory
- Templating a Deployment with the {{ .Values.* }} and {{ .Release.Name }} syntax
- Validating a chart with helm lint and helm template --set without installing it
FAQ
What files does a minimal Helm chart need?
A minimal chart needs three files - Chart.yaml with the metadata (apiVersion v2, name, version), values.yaml with the default inputs, and a manifest under templates/ (such as deployment.yaml) that reads those values. Everything else in a chart is optional convention.
What is the difference between helm lint and helm template?
helm lint checks that the chart is structurally valid - required Chart.yaml fields, parseable templates, valid YAML - and reports how many charts passed or failed. helm template renders the chart into the actual Kubernetes manifests and prints them, letting you verify the output before any install. Neither command contacts a cluster.
How do I override a value when rendering a Helm chart?
Pass --set key=value to helm template or helm install, for example helm template web chart/ --set replicaCount=5. The override replaces the default from values.yaml for that render only. If the output does not change, your template is hard-coding the value instead of reading it from .Values.
How do I test a Helm chart without installing it to a cluster?
Run helm lint chart/ to catch structural errors, then helm template <release> chart/ to render the manifests locally. Together they confirm the chart is valid and produces the YAML you expect, so you can validate charts in CI with no cluster access.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →