TT Lab
Get started
Learn Learning paths Courses

Authoring and Shipping Helm Charts

Scaffolding a Chart and Adding Standard Labels

Continue in TT Lab

Goal

You build the standard structure of a Helm chart yourself and manage names and labels in one place so that consistent standard labels are attached to every object.

Why it matters

When learning charts, people are curious about template syntax first, but what actually determines a chart's lifespan is its structure. Chart.yaml is responsible for what this bundle is, values.yaml for what users may touch, and templates/ for what shape those values take. Only with this separation does the habit of "copying the file for each environment and editing it" disappear. Labels are not a matter of taste either. app.kubernetes.io/* is the standard officially recommended by Kubernetes, and management tools and dashboards group resources by these labels. In particular, you must separate the common labels from the selector labels. A Deployment's spec.selector is a field that cannot be changed after creation, but the common labels have the chart version and the app version mixed in, so if you use them as they are in the selector, the upgrade is rejected the moment you raise the version.

Steps

  1. Create a chart skeleton at /root/helm/lab/labhub-web (you can run helm create labhub-web inside /root/helm/lab). The three files Chart.yaml, values.yaml, and .helmignore, plus templates/ (3 or more files) and the charts/ directory must all exist. Also create the /root/helm/lab/out directory in advance to hold the outputs.
  2. Fill in /root/helm/lab/labhub-web/Chart.yaml. It must have apiVersion: v2, name: labhub-web, type: application, a SemVer such as 0.1.0 for version, appVersion: "1.27", and a one-line description.
  3. Set the defaults in /root/helm/lab/labhub-web/values.yaml as follows: replicaCount: 2, image.repository: nginx, image.tag: "1.27", image.pullPolicy: IfNotPresent, service.port: 80. This file must have at least one comment line starting with #.
  4. /root/helm/lab/labhub-web/templates/_helpers.tpl must have three definitions, labhub-web.fullname, labhub-web.labels, and labhub-web.selectorLabels, and the place that builds the name must include trunc 63 handling. And /root/helm/lab/labhub-web/templates/deployment.yaml must call and use these definitions in the form include "labhub-web....
  5. Save the rendering with helm template labhub-web /root/helm/lab/labhub-web > /root/helm/lab/out/rendered.yaml. The result must contain 2 or more objects, the Deployment's metadata.labels must have app.kubernetes.io/managed-by: Helm, app.kubernetes.io/name: labhub-web, and app.kubernetes.io/version, and the Service's metadata.labels must carry the same common labels.
  6. Make /root/helm/lab/labhub-web/templates/NOTES.txt refer to at least one each of .Release.Name and a value starting with .Values.. Then save the actually rendered notes to /root/helm/lab/out/notes.txt. You can cut out only the part under NOTES: from the output of helm install labhub-web /root/helm/lab/labhub-web --dry-run (sed -n '/^NOTES:/,$p'). The saved file must contain labhub-web and must not have any unrendered brace syntax left.
  7. Save the lint result with helm lint /root/helm/lab/labhub-web > /root/helm/lab/out/lint.txt. The file must have the lint summary line and no [ERROR] at all.
  8. Save a rendering with overridden values using helm template labhub-web /root/helm/lab/labhub-web --set replicaCount=5 > /root/helm/lab/out/scaled.yaml. There are four final checks. The Deployment spec.replicas in /root/helm/lab/out/rendered.yaml is 2, the one in /root/helm/lab/out/scaled.yaml is 5, the container image is nginx:1.27, and the Service's first port is 80. And the app.kubernetes.io/name in the Service's spec.selector must have the same value as the app.kubernetes.io/name in the Deployment Pod template labels.

Notes

Create the chart skeleton

Create a chart skeleton at /root/helm/lab/labhub-web (you can run helm create labhub-web inside /root/helm/lab). The three files Chart.yaml, values.yaml, and .helmignore, plus templates/ (3 or more files) and the charts/ directory must all exist. Also create the /root/helm/lab/out directory in advance to hold the outputs.

Helm has a command that creates the standard structure in one go. After it is created, check that all five places exist: Chart.yaml, values.yaml, .helmignore, templates/, and charts/. charts/ must exist even if it is empty.

Fill in the identity in Chart.yaml

Fill in /root/helm/lab/labhub-web/Chart.yaml. It must have apiVersion: v2, name: labhub-web, type: application, a SemVer such as 0.1.0 for version, appVersion: "1.27", and a one-line description.

The apiVersion in Helm 3 is fixed to one value. And note that the chart's own version and the app version are different fields — think about which one must match the image tag.

Design the defaults in values.yaml

Set the defaults in /root/helm/lab/labhub-web/values.yaml as follows: replicaCount: 2, image.repository: nginx, image.tag: "1.27", image.pullPolicy: IfNotPresent, service.port: 80. This file must have at least one comment line starting with #.

Do not lay out related values flat; group them like image. And since values.yaml is the only documentation the user reads, it must have at least one comment.

Pull names and labels out into helpers

/root/helm/lab/labhub-web/templates/_helpers.tpl must have three definitions, labhub-web.fullname, labhub-web.labels, and labhub-web.selectorLabels, and the place that builds the name must include trunc 63 handling. And /root/helm/lab/labhub-web/templates/deployment.yaml must call and use these definitions in the form include "labhub-web....

Template files that start with an underscore are not rendered as manifests. Think about why the common labels and the selector labels must be defined separately. The name needs length-limit handling.

Attach standard labels to every object

Save the rendering with helm template labhub-web /root/helm/lab/labhub-web > /root/helm/lab/out/rendered.yaml. The result must contain 2 or more objects, the Deployment's metadata.labels must have app.kubernetes.io/managed-by: Helm, app.kubernetes.io/name: labhub-web, and app.kubernetes.io/version, and the Service's metadata.labels must carry the same common labels.

The same common labels must be attached not only to the Deployment but also to the Service. Do not write the managed-by value by hand; take it from the release information. You must save the rendering result to a file for it to be graded.

Write and render the installation notes

Make /root/helm/lab/labhub-web/templates/NOTES.txt refer to at least one each of .Release.Name and a value starting with .Values.. Then save the actually rendered notes to /root/helm/lab/out/notes.txt. You can cut out only the part under NOTES: from the output of helm install labhub-web /root/helm/lab/labhub-web --dry-run (sed -n '/^NOTES:/,$p'). The saved file must contain labhub-web and must not have any unrendered brace syntax left.

NOTES.txt is also a template. You need to use the release name and values together so the user knows what was installed. The file you save must contain the rendered result, and braces must not remain.

Get the chart through lint

Save the lint result with helm lint /root/helm/lab/labhub-web > /root/helm/lab/out/lint.txt. The file must have the lint summary line and no [ERROR] at all.

Keep the whole lint output in a file. If there is even one ERROR, it is a failure. Warnings pass, but it is good to read why they appeared.

Verify the rendering result

Save a rendering with overridden values using helm template labhub-web /root/helm/lab/labhub-web --set replicaCount=5 > /root/helm/lab/out/scaled.yaml. There are four final checks. The Deployment spec.replicas in /root/helm/lab/out/rendered.yaml is 2, the one in /root/helm/lab/out/scaled.yaml is 5, the container image is nginx:1.27, and the Service's first port is 80. And the app.kubernetes.io/name in the Service's spec.selector must have the same value as the app.kubernetes.io/name in the Deployment Pod template labels.

You need two renderings: the default one and the one with overridden values. And compare with your own eyes whether the Service's selector and the Pod template's labels have the same key and value. If they differ, traffic does not reach the Pods.