Skip to main content

Lesson: Writing Your First Chart

What you'll learn

  • How to scaffold a chart with helm create and 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 version in Chart.yaml every time you change the chart — repos and upgrades rely on it.
  • Never hardcode what should be a value (image tag, hostname, replicas).
  • Read helm template output before installing — treat it like reviewing a diff.
  • Keep your per-environment values files in Git (Module 4) so installs are reproducible.

Dig deeper

Search terms

  • helm create chart tutorial
  • helm template nindent indent helpers
  • helm lint template install workflow
  • helm package chart versioning
  • helm push oci registry chart

Check yourself

  1. Why start from helm create and then delete files, rather than writing a chart from scratch?
  2. In the Deployment template, where does replicas: 2 ultimately come from?
  3. What does nindent do, and why does it matter in a chart template?
  4. List the lint → render → install → verify loop and what each step catches.
  5. What does helm package produce, and why must you bump version in Chart.yaml when you change a chart?