Skip to content
Commitpedia
Log in Join & edit

Package a web app as a Helm chart: create, test, install, upgrade and roll back

Turn a small nginx web app into your own Helm chart, check it with lint and template, install it into a namespace, upgrade it with new values, roll back a release and uninstall it cleanly.

Not checked by a trusted editor yet Last edited Outdated?

Edit history of Package a web app as a Helm chart: create, test, install, upgrade and roll back: 1 recorded edit
Edit history · 1 edit
Type
Article
Edits
1
Editors
1
First issued
15 Oct 2026
Last edit
15 Oct 2026
Reading
8 min

Helm is the package manager for Kubernetes. A chart is a folder of templated Kubernetes manifests plus a file of default settings, and every installation of a chart is a release with its own numbered history. This tutorial walks through the full life of your own chart, from helm create to helm uninstall.

It is for people who can already deploy a Deployment and a Service with kubectl and want a repeatable way to install the same app with different settings. The commands were checked with Helm 4; the chart workflow is the same in Helm 3 except where noted.

What you will build

  • A chart called hello-web that runs nginx and serves a page whose text comes from a value.
  • A new ConfigMap template and an edited Deployment template, including a checksum annotation that restarts Pods when the page changes.
  • A release called demo in its own namespace, upgraded with a production-style values file.
  • A rollback to the first revision, a look at the release history, and a clean uninstall.

Before you start

  • A Kubernetes cluster you may use for testing, such as a local kind cluster, and kubectl configured for it. Check with kubectl config current-context.
  • Helm installed (brew install helm on macOS, or a method from the Helm installation guide). Check with helm version.
  • A text editor and about 45 to 60 minutes.
  • Knowledge assumed: YAML, Deployments, Services and ConfigMaps. Helpful background: Helm basics (installing a published chart and overriding values) and Go template syntax at the level of {{ .Values.x }}.

Steps 1 to 4 run entirely on your computer. The cluster is needed from Step 5.

Step 1: Create the chart

helm create writes a working starter chart for an nginx web server. Starting from it is faster than writing a chart from nothing, and it follows Helm’s naming and labeling conventions.

helm create hello-web
find hello-web -type f | sort

You should see Chart.yaml (name and versions), values.yaml (default settings), .helmignore, and in templates/ the Deployment, Service, ServiceAccount, Ingress, HPA, an optional Gateway API HTTPRoute, NOTES.txt, _helpers.tpl and tests/test-connection.yaml. The exact list depends on your Helm version.

Step 2: Set the app version in Chart.yaml

Chart.yaml has two versions. version is the version of the chart itself and should change whenever the chart changes. appVersion is the version of the app it deploys; the starter Deployment uses it as the image tag when image.tag is empty. Open hello-web/Chart.yaml and set:

apiVersion: v2
name: hello-web
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: "1.27"

With image.repository: nginx from the default values, the chart now deploys nginx:1.27.

Step 3: Add a template and edit the Deployment

Templates are Kubernetes manifests with placeholders. .Values holds the merged values, .Release the release name and namespace, and include calls the named helpers in _helpers.tpl. Create hello-web/templates/configmap.yaml with a page built from a value:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "hello-web.fullname" . }}-html
  labels:
    {{- include "hello-web.labels" . | nindent 4 }}
data:
  index.html: |
    <h1>{{ .Values.message }}</h1>

Now edit hello-web/templates/deployment.yaml in two places. First, replace the podAnnotations block under template: so it always adds a checksum of the rendered ConfigMap. When the page text changes, the checksum changes, the Pod template changes, and Kubernetes rolls out new Pods. This pattern comes from Helm’s chart tips.

    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
        {{- with .Values.podAnnotations }}
        {{- toYaml . | nindent 8 }}
        {{- end }}

Second, replace the two {{- with .Values.volumeMounts }} and {{- with .Values.volumes }} blocks at the end of the container and Pod spec with a fixed mount of the ConfigMap over the nginx web root. Keep the indentation exactly as shown: volumeMounts lines up with resources inside the container, and volumes lines up with containers.

          volumeMounts:
            - name: html
              mountPath: /usr/share/nginx/html
              readOnly: true
      volumes:
        - name: html
          configMap:
            name: {{ include "hello-web.fullname" . }}-html

Step 4: Edit values, then lint and render

Values are the chart’s settings. In hello-web/values.yaml, replace the line resources: {} (and the commented example under it) with real requests and a memory limit, and add a message value at the end of the file:

resources:
  requests:
    cpu: 50m
    memory: 64Mi
  limits:
    memory: 128Mi

message: "Hello from Helm"

Before going near a cluster, check the chart. helm lint looks for structural problems and template errors. helm template renders the manifests locally so you can read exactly what would be sent to Kubernetes.

helm lint ./hello-web
helm template demo ./hello-web --namespace web-demo > rendered.yaml
helm template demo ./hello-web --show-only templates/configmap.yaml --set message="Hi"

helm lint should end with 1 chart(s) linted, 0 chart(s) failed; an [INFO] line recommending an icon is harmless. rendered.yaml should contain a ServiceAccount, the ConfigMap, a Service, the Deployment with image nginx:1.27 and the checksum annotation, and the test Pod. The last command prints the ConfigMap with <h1>Hi</h1>, which shows how --set overrides a value.

If helm template fails with a YAML or indentation error, the message names the template file and line. Compare your edits with the snippets above.

Step 5: Install the chart into a namespace

Install the chart as a release named demo. --create-namespace creates the namespace if needed, and --wait makes Helm wait until the resources are ready instead of returning immediately.

helm install demo ./hello-web --namespace web-demo --create-namespace --wait
helm list --namespace web-demo
kubectl get deployment,pods,service,configmap --namespace web-demo

helm list shows demo with revision 1 and status deployed. You should see one Pod running and a Service called demo-hello-web. Helm also prints the rendered NOTES.txt, which explains how to reach the app. Test it through a port forward in a second terminal:

kubectl --namespace web-demo port-forward service/demo-hello-web 8080:80
curl http://localhost:8080/
helm test demo --namespace web-demo

curl returns <h1>Hello from Helm</h1>. helm test runs the test Pod from templates/tests/, which fetches the Service with wget, and reports whether it succeeded.

Step 6: Upgrade with a values file

Settings for each environment usually live in their own values file, kept in Git next to the chart. Create values-prod.yaml outside the chart folder:

replicaCount: 3
message: "Hello from Helm, version 2"

Preview the change, then upgrade. Values from -f files are merged over the chart’s values.yaml; later files and --set win over earlier ones.

helm template demo ./hello-web --namespace web-demo -f values-prod.yaml --show-only templates/deployment.yaml | grep -E 'replicas|checksum'
helm upgrade demo ./hello-web --namespace web-demo -f values-prod.yaml --wait
helm history demo --namespace web-demo
helm get values demo --namespace web-demo

The history now has revision 1 (superseded) and revision 2 (deployed). helm get values shows only the values you supplied for this revision. kubectl get pods -n web-demo shows three Pods, all new because the checksum changed, and curl through the port forward (start it again if it stopped) returns the version 2 message.

Note that helm upgrade without -f or --set uses the chart defaults again. If you want to keep the values from the last release and change only one, add --reuse-values or --reset-then-reuse-values, and be explicit about which you use in scripts.

Step 7: Roll back a release

A rollback puts the manifests and values of an earlier revision back in place. It does not delete history: it creates a new revision that is a copy of the old one.

helm rollback demo 1 --namespace web-demo --wait
helm history demo --namespace web-demo
kubectl get pods --namespace web-demo

The history gains revision 3 with the description Rolled back to 1. You are back to one replica and the original message. If you leave out the revision number, Helm rolls back to the previous release. For safer automated upgrades, Helm 4 offers --rollback-on-failure on helm upgrade (Helm 3 called this --atomic), which rolls back automatically if the upgrade fails.

Step 8: Package the chart

To share a chart, you package it into a versioned archive that can be pushed to a chart repository or an OCI registry. Raise version in Chart.yaml whenever the chart changes.

helm package ./hello-web

Helm prints Successfully packaged chart and saved it to: ...hello-web-0.1.0.tgz. You can install from the archive just like from the folder: helm upgrade --install demo ./hello-web-0.1.0.tgz -n web-demo.

Check that it works

helm lint ./hello-web -f values-prod.yaml
helm status demo --namespace web-demo
helm history demo --namespace web-demo
kubectl get pods --namespace web-demo
  • Lint reports 0 failed charts for both the default and the production values.
  • helm status says STATUS: deployed for the latest revision.
  • The history shows install, upgrade and rollback as revisions 1, 2 and 3.
  • The Pods are running and ready, and curl through a port forward returns the page for the current revision.

If something goes wrong

  • Install fails with an error that a release name is already in use. Release names are unique per namespace. Check with helm list -n web-demo --all, then either upgrade the existing release or uninstall it first.
  • helm install --wait times out. Helm only reports that resources did not become ready. Look at the Pods with kubectl get pods -n web-demo and kubectl describe pod, and fix the cause, often an image name typo or a failing probe.
  • A template renders invalid YAML. Run helm template with --debug to see the generated output, and check indentation around nindent. A wrong number in nindent is the most common cause.
  • Changing message does not restart the Pods. Check that the checksum annotation is in the rendered Deployment. Without it, the ConfigMap changes but the Pod template does not.
  • An upgrade unexpectedly lost your settings. You upgraded without the values file. Run helm get values demo -n web-demo --revision N to see what an older revision used, then upgrade again with the right -f files.

Clean up

Warning: helm uninstall deletes every resource of the release, and deleting the namespace removes anything else left in it. Check first that you are in the right cluster and namespace:

kubectl config current-context
helm list --namespace web-demo
helm uninstall demo --namespace web-demo
kubectl delete namespace web-demo

helm uninstall also deletes the release history unless you add --keep-history. Helm does not delete the namespace for you, which is why the last command is separate. Locally you can remove the hello-web folder, the .tgz, rendered.yaml and values-prod.yaml.

Next steps

  • Add a values.schema.json so Helm rejects wrong value types before rendering.
  • Enable the starter Ingress template with values and publish the app on a host name.
  • Push the packaged chart to an OCI registry with helm push and install it from there.
  • Run helm lint and helm template in a CI pipeline on every change to the chart.
  • Add a dependency, such as a database chart, in Chart.yaml and manage it with helm dependency update.

Official documentation

See also

Repos in the wild

  • helm/helm helm/helm The Helm package manager for Kubernetes: the helm CLI and Go SDK for installing, upgrading and rolling back charts. GitHub

History

Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.

  1. Commitpedia +11,906 chars

Report a problem

Something wrong, unsafe or copied? Tell the editors.
What is wrong?