Authoring and Shipping Helm Charts
Scaffolding a Chart and Adding Standard Labels
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
- Create a chart skeleton at
/root/helm/lab/labhub-web(you can runhelm create labhub-webinside/root/helm/lab). The three filesChart.yaml,values.yaml, and.helmignore, plustemplates/(3 or more files) and thecharts/directory must all exist. Also create the/root/helm/lab/outdirectory in advance to hold the outputs. - Fill in
/root/helm/lab/labhub-web/Chart.yaml. It must haveapiVersion: v2,name: labhub-web,type: application, a SemVer such as0.1.0forversion,appVersion: "1.27", and a one-linedescription. - Set the defaults in
/root/helm/lab/labhub-web/values.yamlas 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#. /root/helm/lab/labhub-web/templates/_helpers.tplmust have three definitions,labhub-web.fullname,labhub-web.labels, andlabhub-web.selectorLabels, and the place that builds the name must includetrunc 63handling. And/root/helm/lab/labhub-web/templates/deployment.yamlmust call and use these definitions in the forminclude "labhub-web....- 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'smetadata.labelsmust haveapp.kubernetes.io/managed-by: Helm,app.kubernetes.io/name: labhub-web, andapp.kubernetes.io/version, and the Service'smetadata.labelsmust carry the same common labels. - Make
/root/helm/lab/labhub-web/templates/NOTES.txtrefer to at least one each of.Release.Nameand 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 underNOTES:from the output ofhelm install labhub-web /root/helm/lab/labhub-web --dry-run(sed -n '/^NOTES:/,$p'). The saved file must containlabhub-weband must not have any unrendered brace syntax left. - 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. - 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 Deploymentspec.replicasin/root/helm/lab/out/rendered.yamlis 2, the one in/root/helm/lab/out/scaled.yamlis 5, the container image isnginx:1.27, and the Service's first port is 80. And theapp.kubernetes.io/namein the Service'sspec.selectormust have the same value as theapp.kubernetes.io/namein the Deployment Pod template labels.
Notes
- Lab Pods start fresh for every lab, so charts or releases made in other labs do not remain. You build the charts you need from scratch here each time — this is where the fact that a chart is a reproducible package shows.
- The default chart that the skeleton-creating command makes already contains helpers and standard labels. It is faster to adjust the values and names than to delete it and write anew.
helm templateworks without a cluster, andhelm install --dry-runcreates nothing on the cluster while still showing NOTES. When you check the notes, you need the latter.- Common mistake 1: leaving
appVersionas it is. The default image tag and theapp.kubernetes.io/versionlabel come from here, so the values drift. - Common mistake 2: copying the original
NOTES.txtas is to/root/helm/lab/out/notes.txt. The braces remain, so it is judged not to be the rendered result.
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.