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.

Kubernetes Engineerkuberneteshelmcharts

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:

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

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

Package an App as a Helm ChartKubernetes projectWrite a Kubernetes Deployment ManifestKubernetes projectMount a ConfigMap in a PodKubernetes projectKubernetes roadmapStep by step to hiredKubernetes interview questionsSTAR answersAll Kubernetes projectsProjects hub

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 →