Lesson: Writing Your First Chart
What you'll learn
- How to scaffold a chart with
helm createand trim it down. - How to template a Deployment and Service from
values.yaml. - How to lint, render, and install your own chart.
- How to package and (optionally) share it.
By the end you'll have authored a small but real chart for an app you control.
The lesson
1. Scaffold, then simplify
helm create gives you a working starter chart — but it's large. The fastest way to learn is to scaffold it, then strip it to the essentials:
helm create myapp
# keep: Chart.yaml, values.yaml, templates/deployment.yaml, templates/service.yaml,
# templates/_helpers.tpl, templates/NOTES.txt
# delete the rest (hpa, serviceaccount, tests, ingress) until you need them
You can always add pieces back. Starting minimal keeps the templating understandable.
2. Define your knobs in values.yaml
Decide what should be configurable, and give sensible defaults:
replicaCount: 2
image:
repository: 10.100.100.6/myapp # the lab registry from Module 6
tag: "1.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
targetPort: 8080
3. Template the Deployment
The template references those values. Note _helpers.tpl's myapp.fullname for consistent naming/labels:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
labels: {{- include "myapp.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels: {{- include "myapp.selectorLabels" . | nindent 6 }}
template:
metadata:
labels: {{- include "myapp.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.targetPort }}
nindent indents a block by N spaces — essential because YAML cares about indentation and the helper output must line up.
4. Template the Service
apiVersion: v1
kind: Service
metadata:
name: {{ include "myapp.fullname" . }}
spec:
type: {{ .Values.service.type }}
selector: {{- include "myapp.selectorLabels" . | nindent 4 }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}
5. The author's loop: lint → render → install
Do these in order every time you change the chart:
helm lint ./myapp # 1. structural/style problems?
helm template demo ./myapp # 2. read the rendered YAML — is it what you expect?
helm install demo ./myapp -n demo --create-namespace # 3. deploy to the lab cluster
kubectl get all -n demo # 4. confirm the objects exist and are Ready
helm upgrade demo ./myapp --set replicaCount=3 # 5. change a value, upgrade
edit chart ─▶ helm lint ─▶ helm template (read it) ─▶ helm install/upgrade ─▶ kubectl verify
▲ │
└───────────────────────── iterate ────────────────────────────────────┘
6. Package and share (optional)
When the chart is good, package it into a versioned archive:
helm package ./myapp # produces myapp-0.1.0.tgz (uses version from Chart.yaml)
That .tgz can be hosted in a chart repository so others install it with helm install … myrepo/myapp. Modern Helm can also push charts to OCI registries (like the lab registry at 10.100.100.6) with helm push — the same place your container images live (Module 6).
7. Good habits
- Bump
versioninChart.yamlevery time you change the chart — repos and upgrades rely on it. - Never hardcode what should be a value (image tag, hostname, replicas).
- Read
helm templateoutput before installing — treat it like reviewing a diff. - Keep your per-environment values files in Git (Module 4) so installs are reproducible.
Dig deeper
- Helm — Chart Template Guide: "Getting Started"
- Helm — "Built-in Objects" and template functions
- Helm — Charts: best practices
- Helm —
helm package/ OCI registries
Search terms
helm create chart tutorialhelm template nindent indent helpershelm lint template install workflowhelm package chart versioninghelm push oci registry chart
Check yourself
- Why start from
helm createand then delete files, rather than writing a chart from scratch? - In the Deployment template, where does
replicas: 2ultimately come from? - What does
nindentdo, and why does it matter in a chart template? - List the lint → render → install → verify loop and what each step catches.
- What does
helm packageproduce, and why must you bumpversioninChart.yamlwhen you change a chart?
No comments to display
No comments to display