Package and Deploy a Web App with Helm
Build a Helm chart from scratch to deploy, upgrade, and roll back a web app.
Introduction
30 Second Summary
Every time you deploy an app to Kubernetes, you write YAML files describing what you want. The moment you need to change settings across files or recover from a bad update, those raw files become a liability.
In this project, you will build a Helm chart from scratch that packages a web application for Kubernetes deployment. You will deploy, upgrade, and roll back your app using single commands instead of manually editing files.
What You'll Build
By the end, you will deploy your web app with one command, break it intentionally with a bad image tag, and recover instantly with a single rollback.
By the end of this project, you'll have:
- A single helm install command that deploys your web app to Kubernetes from a chart you built from scratch.
- The power to override any configuration (replicas, image version, ports) with a flag instead of opening and editing YAML files.
- A proven rollback safety net where you intentionally break your app with a bad deploy, then recover to a working state with one command and inspect the full release history.
- Secret Mission: Deploy the same chart to two environments (dev and prod) with different configurations using values files.
Are there any prerequisites?
This project assumes basic Kubernetes knowledge (Deployments, Services, and kubectl apply). Familiarity with YAML is also helpful.
You will need Docker Desktop with Kubernetes enabled, kubectl, and VS Code already installed on your machine.
Before We Start
Before diving in, take a moment to lock in what you are building and why it matters.
Set Up Helm
To package and deploy your web app with Helm, you first need the Helm CLI available on your machine. Helm is the tool that talks to your Kubernetes cluster and manages your application releases.
In this step, you will install Helm and confirm that your local Kubernetes cluster is up and healthy. Once both are ready, you have everything you need to start deploying.
In this step, get ready to:
- Install the Helm CLI and verify it is on your PATH.
- Confirm your Docker Desktop Kubernetes cluster is running and reachable.
Install the Helm CLI
Helm is a command-line tool that you run from your terminal. Let's check whether it is already installed and at the right version.
- Open your terminal.
- Check if Helm is already installed by running:
helm version
Look at what your terminal prints and pick the tab that matches your result.
✔️ I see version 4.x or higher
Your terminal printed version info starting with v4 (for example v4.2.3). Helm is already installed and up to date.
You are ready to move on to verifying your Kubernetes cluster below.
ⓧ I see an older version (v3.x)
Your terminal printed a version starting with v3. Helm 3 bug fixes ended on July 8th, 2026 and security fixes end November 11th, 2026. You need Helm 4 for this project.
- Upgrade Helm by running:
winget install Helm.Helm
What does this command do?
The winget install command downloads and installs (or upgrades) the Helm binary from the official package registry. It places the helm executable on your system PATH so you can call it from any terminal window.
- Close your terminal window completely, then reopen it. This refreshes your PATH so the new version is picked up.
- Verify the upgrade by running:
helm version
You should now see a version string that includes v4.
Still seeing the old version?
Make sure you fully closed and reopened your terminal after running the install command. Some terminals cache the old PATH until they are restarted.
If you use multiple terminal apps (PowerShell, Windows Terminal, Git Bash), reopen the one you plan to use for this project.
help me troubleshoot my Helm version after upgrading with winget
ⓧ Command not found
Your terminal could not find a helm command. That means Helm is not installed yet.
- Install Helm by running:
winget install Helm.Helm
What does this command do?
The winget install command downloads and installs the Helm binary from the official package registry. It places the helm executable on your system PATH so you can call it from any terminal window.
- Close your terminal window completely, then reopen it. This refreshes your PATH so the system can find the new helm command.
- Verify the installation by running:
helm version
You should see a version string that includes v4.
Still seeing 'command not found'?
Make sure you fully closed and reopened your terminal after running the install command. The PATH update only takes effect in new terminal sessions.
If winget itself is not recognised, you can install Helm using Chocolatey (choco install kubernetes-helm) or Scoop (scoop install helm) instead.
help me install Helm on Windows
Verify your Kubernetes cluster is running
Helm deploys applications to a Kubernetes cluster. Your Docker Desktop installation includes a built-in single-node cluster. Let's confirm it is up and healthy.
Before you run the next command, what do you think the status of your node will be if Docker Desktop's Kubernetes is enabled and running?
- Check your cluster nodes by running:
kubectl get nodes
You should see one node named docker-desktop with a STATUS of Ready.
- Confirm full cluster connectivity by running:
kubectl cluster-info
You should see output showing the Kubernetes control plane running at a local address (such as https://kubernetes.docker.internal:6443).
Node not showing Ready?
Open Docker Desktop and check that Kubernetes is enabled under Settings then Kubernetes. Make sure the Enable Kubernetes checkbox is ticked.
If you just enabled it, Docker Desktop needs a minute or two to download the cluster components. Wait for the Kubernetes status indicator in the bottom-left of Docker Desktop to turn green, then retry the command.
help me get my Docker Desktop Kubernetes node to Ready status
Your tools are ready. Next up, you will deploy a web app the hard way using raw Kubernetes manifests to experience the pain that Helm was built to solve.
Deploy the Hard Way
Your tools are ready. In this step, you will deploy a web application to Kubernetes the traditional way, using raw YAML manifest files.
This approach works, but you will quickly notice its limitations. Hardcoded values scattered across multiple files make changes tedious and error-prone. This is the exact problem Helm solves.
In this step, get ready to:
- Create a project directory with raw Kubernetes manifest files.
- Deploy the manifests to your local cluster and see the app running.
- Experience the pain of hardcoded configuration across multiple files.
Create a project directory and raw manifest files
Kubernetes uses YAML files called manifests to describe what you want running in the cluster. You will write two manifests: one for a Deployment (the pods) and one for a Service (the network access).
- Move to your Desktop by running this command:
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder with a subfolder for raw manifests, then move into it by running these commands:
mkdir -p helm-project/raw-manifests
cd helm-project
- Confirm the folder structure exists by running:
ls
You should see raw-manifests listed in the output.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Select the helm-project folder on your Desktop, then click Open.
You should see the raw-manifests subfolder in the VS Code Explorer sidebar on the left.
- Right-click the raw-manifests folder in the Explorer sidebar and select New File.
- Name the file deployment.yaml and press Enter.
- Paste the following code into deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
labels:
app: webapp
spec:
replicas: 2
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: webapp
image: nginx:stable
ports:
- containerPort: 80
What does this manifest do?
- replicas: 2 tells Kubernetes to run two identical copies (pods) of your app for reliability.
- image: nginx:stable specifies the container image each pod runs. The official nginx image serves a default welcome page.
- The labels connect everything together. The Service you create next uses these labels to find the right pods.
- Save deployment.yaml.
File not appearing in Explorer?
Make sure you right-clicked the raw-manifests folder specifically, not the root helm-project folder. The file should appear nested inside raw-manifests.
Help me create deployment.yaml in the right location
- Right-click the raw-manifests folder again and select New File.
- Name the file service.yaml and press Enter.
- Paste the following code into service.yaml:
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30080
selector:
app: webapp
What does this manifest do?
- type: NodePort exposes your app on a static port on the node itself. This lets you access it at localhost:30080 from your browser.
- nodePort: 30080 is the specific port on your machine that maps to the pods. NodePort values must fall between 30000 and 32767.
- The selector matches pods with the label app: webapp so traffic reaches the right Deployment.
- Save service.yaml.
- Confirm both files exist by switching to your terminal and running:
ls raw-manifests/
You should see deployment.yaml and service.yaml listed.
Files not showing up?
Make sure your terminal is still inside the helm-project directory. Run pwd to check your current location. If you are somewhere else, run cd ~/Desktop/helm-project to get back.
Help me find my manifest files
✔️ Awesome, I've got everything!
Great. Make sure both files are saved, then continue to deploy them.
ⓧ I'd like to double check the full code
Here is the full deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
labels:
app: webapp
spec:
replicas: 2
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: webapp
image: nginx:stable
ports:
- containerPort: 80
And here is the full service.yaml:
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30080
selector:
app: webapp
Deploy to Kubernetes with kubectl
Your manifests describe what you want. Now you will hand them to Kubernetes and watch it create the resources.
- Deploy both manifests to your cluster by running:
kubectl apply -f raw-manifests/
What does this command do?
kubectl apply -f raw-manifests/ reads every YAML file in the folder and sends them to the Kubernetes API server. Kubernetes then creates the Deployment (which spins up pods) and the Service (which routes traffic to them).
You should see output confirming both resources were created:
deployment.apps/webapp created
service/webapp created
Before you check the pods, predict what you expect to see. How many pods should be running, based on the replicas value you set?
- Check your pods are running:
kubectl get pods
You should see 2 pods with STATUS: Running and READY: 1/1. This matches the replicas: 2 you defined in deployment.yaml.
- Open your browser and go to http://localhost:30080.
You should see the "Welcome to nginx!" page. Your app is live on Kubernetes.
Page not loading in your browser?
Run kubectl get svc webapp and confirm the NODE-PORT column shows 30080. If you see pending in the EXTERNAL-IP column, that is normal for NodePort services.
Also confirm Docker Desktop has Kubernetes enabled. Open Docker Desktop, go to Settings then Kubernetes, and confirm the checkbox is ticked and the status shows green.
Help me troubleshoot my NodePort service
Feel the pain of raw manifests
Your app is running. Now imagine a real scenario: your team needs a staging environment with a different replica count, a different image version, and a different app name.
Why are raw manifests painful?
Look at your two files. The app name webapp is hardcoded in 6 places across both manifests. The image tag nginx:stable is buried inside the Deployment spec. The replica count is a magic number with no label explaining what it controls.
To create a staging environment, you would have to open each file, find every hardcoded value, and manually edit them. There is no versioning, no rollback, and no single source of configuration.
This is exactly what Helm fixes. It turns these static files into templates with configurable values, so one chart serves any environment.
Your app is running, but the process of getting here exposed the cracks in raw manifest management. Next up, you will build a Helm chart that fixes every one of these pain points.
Create Your Helm Chart
In the previous step, you deployed your web app using raw manifests and saw the pain of hardcoded values scattered across files.
A Helm chart solves this by packaging your Kubernetes manifests as templates with configurable values. In this step, you'll build a chart from scratch so one set of templates can serve any configuration.
In this step, get ready to:
- Scaffold a Helm chart and strip out the generated boilerplate.
- Write custom deployment and service templates using Helm's template syntax.
- Define default configuration values and validate your chart.
Scaffold a new chart and strip it down
Helm provides a scaffolding command that generates a chart skeleton with sensible defaults. The generated boilerplate is too complex for learning, so you'll strip it and write your own templates.
- Make sure your terminal is inside the helm-project folder, then scaffold a new chart by running this command:
helm create webapp-chart
What does helm create generate?
This command creates a full chart directory structure including Chart.yaml (metadata), values.yaml (defaults), a templates/ folder (with pre-made Kubernetes manifests), and a charts/ folder (for dependencies).
The generated templates include deployment, service, ingress, HPA, and serviceaccount manifests. These are powerful but overly complex when you are learning Helm for the first time.
- Confirm the chart was created by running:
ls webapp-chart
You should see Chart.yaml, charts, templates, and values.yaml listed.
- In VS Code's Explorer sidebar, expand the webapp-chart/templates/ folder.
- Select all files inside templates/ (including the tests/ subfolder), right-click, and select Delete.
- If the webapp-chart/charts/ folder contains any files, delete those too.
- Verify the templates folder is now empty by running:
ls webapp-chart/templates
You should see no files listed, or an empty output confirming the folder is clear.
Still seeing files in templates/?
Make sure you deleted the tests/ subfolder as well, not just the top-level files. Also check that you are looking at webapp-chart/templates/ and not the raw-manifests/ folder.
help me delete the boilerplate templates from my Helm chart
Write your own chart templates from scratch
Helm templates are standard YAML files with Go template placeholders. Anywhere you see {{ .Values.something }}, Helm injects the matching value from values.yaml at deploy time.
What is Helm template syntax?
Helm uses double curly braces {{ }} to mark dynamic values. Inside the braces, .Values refers to your values.yaml file, and .Release.Name refers to the name you give the release when you install the chart.
This means you write the template once and change behaviour entirely through values, without touching the template files again.
- In VS Code, create a new file at webapp-chart/templates/deployment.yaml and paste the following template:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-webapp
labels:
app: {{ .Release.Name }}-webapp
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Release.Name }}-webapp
template:
metadata:
labels:
app: {{ .Release.Name }}-webapp
spec:
containers:
- name: webapp
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.containerPort }}
What does this template do?
- {{ .Release.Name }}-webapp names resources after whatever release name you choose at install time. This lets you deploy the same chart multiple times without name collisions.
- {{ .Values.replicaCount }} pulls the pod count from your values file, so you can scale up or down without editing this template.
- {{ .Values.image.repository }}:{{ .Values.image.tag }} builds the full container image reference from two separate values, making version bumps a single-value change.
- Save webapp-chart/templates/deployment.yaml.
- Confirm the file exists by running:
ls webapp-chart/templates
You should see deployment.yaml listed.
File not showing up?
Make sure you saved the file inside webapp-chart/templates/ and not in the root webapp-chart/ folder or somewhere else. The file name must be exactly deployment.yaml.
help me create the deployment template in the right location
✔️ Awesome, I've got everything!
Great. Make sure you saved the file before moving on.
ⓧ I'd like to double check the full code
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-webapp
labels:
app: {{ .Release.Name }}-webapp
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Release.Name }}-webapp
template:
metadata:
labels:
app: {{ .Release.Name }}-webapp
spec:
containers:
- name: webapp
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.containerPort }}
- Create another new file at webapp-chart/templates/service.yaml and paste this template:
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-webapp
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.containerPort }}
nodePort: {{ .Values.service.nodePort }}
selector:
app: {{ .Release.Name }}-webapp
What does this template do?
- {{ .Values.service.type }} lets you switch between NodePort, ClusterIP, or LoadBalancer without touching this file.
- {{ .Values.service.nodePort }} makes the external port configurable per environment.
- The selector uses {{ .Release.Name }}-webapp to match the pods created by the deployment template above.
- Save webapp-chart/templates/service.yaml.
- Confirm both templates exist by running:
ls webapp-chart/templates
You should see both deployment.yaml and service.yaml listed.
Only seeing one file?
Double-check that you saved both files inside webapp-chart/templates/. If one is missing, create it again in the correct location.
help me verify my Helm chart templates are in the right place
✔️ Awesome, I've got everything!
Great. Both templates are ready for configuration.
ⓧ I'd like to double check the full code
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-webapp
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.containerPort }}
nodePort: {{ .Values.service.nodePort }}
selector:
app: {{ .Release.Name }}-webapp
Define your default values
Your templates reference values like .Values.replicaCount and .Values.image.repository. Now you need to define what those defaults are in values.yaml.
- In VS Code, open webapp-chart/values.yaml (this file already exists from the scaffold).
- Replace its entire contents with the following:
replicaCount: 2
image:
repository: nginx
tag: stable
pullPolicy: IfNotPresent
service:
type: NodePort
port: 80
nodePort: 30080
containerPort: 80
What do these values control?
- replicaCount: 2 starts 2 pods, matching what your raw manifests had.
- image.repository and image.tag define the container image. Changing the tag is how you deploy new versions.
- service.nodePort: 30080 maps to the same port your raw manifests used, so the app remains accessible at the same URL.
- Anyone installing your chart can override any of these values without editing the file.
- Save webapp-chart/values.yaml.
✔️ Awesome, I've got everything!
Great. Your defaults are set.
ⓧ I'd like to double check the full code
replicaCount: 2
image:
repository: nginx
tag: stable
pullPolicy: IfNotPresent
service:
type: NodePort
port: 80
nodePort: 30080
containerPort: 80
- Open webapp-chart/Chart.yaml and replace its entire contents with:
apiVersion: v2
name: webapp-chart
description: A Helm chart for deploying a web application to Kubernetes
type: application
version: 0.1.0
appVersion: "1.0.0"
What is Chart.yaml?
This file is the chart's identity card. It tells Helm the chart's name, its version (which you bump on each change), and the app version it deploys. The apiVersion: v2 format is compatible with Helm 4.
- Save webapp-chart/Chart.yaml.
✔️ Awesome, I've got everything!
Great. Your chart metadata is ready.
ⓧ I'd like to double check the full code
apiVersion: v2
name: webapp-chart
description: A Helm chart for deploying a web application to Kubernetes
type: application
version: 0.1.0
appVersion: "1.0.0"
Your chart is complete. Before you try to deploy it, what do you think will happen if you ask Helm to validate the chart structure?
- Validate your chart by running:
helm lint webapp-chart
You should see output ending with 1 chart(s) linted, 0 chart(s) failed. This confirms your templates, values, and chart metadata are all well-formed.
Seeing lint errors?
Check that Chart.yaml has the required apiVersion, name, and version fields. Also verify that values.yaml uses proper YAML indentation (2 spaces, no tabs).
If the error mentions a specific template, open that file and look for mismatched {{ }} braces or typos in value paths.
help me fix my Helm chart lint errors
Your chart is validated and ready to deploy. Next up, you'll install it to your cluster with a single Helm command and see your app running from a chart instead of raw manifests.
Deploy with Helm
Your chart is built and validated. Deploying your app to Kubernetes is now a single command instead of managing multiple YAML files by hand.
In this step, you will install your chart with Helm, verify the deployment works, and then change configuration on the fly without editing any files. Helm tracks every deployment as a versioned release, giving you a full history of what was deployed and when.
In this step, get ready to:
- Remove the old raw manifests deployment from your cluster.
- Install your Helm chart and verify the release is running.
- Override values with a Helm upgrade to scale your app.
Clean up the old raw manifests deployment
Before installing with Helm, you need to remove the resources you deployed manually in the previous step. If you leave them running, the Helm chart will create duplicate resources with conflicting NodePort assignments.
- Remove the manually deployed resources by running this command:
kubectl delete -f raw-manifests/
- Confirm the pods are gone by running:
kubectl get pods
You should see No resources found in default namespace or an empty list. The old deployment is completely removed.
Still seeing pods?
Make sure you are running the command from inside your helm-project folder where the raw-manifests/ directory lives. If you moved directories, cd back to helm-project and try again.
help me delete my raw Kubernetes manifests
Install your chart with Helm
With the cluster cleared, you can now deploy using your Helm chart. A single helm install command reads your templates, injects values from values.yaml, and applies the rendered manifests to Kubernetes.
- Install your chart as a release named my-webapp by running:
helm install my-webapp ./webapp-chart
What does this command do?
helm install takes two arguments: the release name (my-webapp) and the path to the chart (./webapp-chart). It renders your templates using the default values, then applies the resulting Kubernetes resources to your cluster.
The release name is important. It becomes part of your resource names (my-webapp-webapp) via the {{ .Release.Name }} template variable.
Before you check the result, what do you think helm list will show? Think about what status a successful install would report.
- Verify the release was created by running:
helm list
You should see your my-webapp release listed with STATUS: deployed.
- Confirm the pods are running by running:
kubectl get pods
You should see 2 pods with names starting with my-webapp-webapp, both showing STATUS: Running.
- Open http://localhost:30080 in your browser to confirm the app is accessible.
You should see the nginx welcome page. This is the same app as before, but now it is managed by Helm as a versioned release.
Not seeing the welcome page?
Run kubectl get svc and check that the my-webapp-webapp service exists with NodePort 30080. If the service is missing, check your webapp-chart/templates/service.yaml for template syntax errors.
help me troubleshoot my Helm deployment
Override values without editing files
One of Helm's biggest advantages is changing configuration without opening a single YAML file. The --set flag lets you override any value defined in values.yaml directly from the command line.
- Scale your deployment to 4 replicas by running:
helm upgrade my-webapp ./webapp-chart --set replicaCount=4
What does this command do?
helm upgrade applies a new configuration to an existing release. The --set replicaCount=4 flag overrides the default replicaCount: 2 in your values.yaml without modifying the file itself.
Helm records this as a new revision. You now have a history of changes you can inspect or roll back to.
- Confirm the scale-up by running:
kubectl get pods
You should see 4 pods running with names starting with my-webapp-webapp.
Before you check the release history, how many revisions do you think Helm has recorded so far? What would each revision represent?
- Inspect your release history by running:
helm history my-webapp
You should see two revisions. Revision 1 is your initial install, and revision 2 is the upgrade that scaled to 4 replicas. Each row shows the REVISION number, STATUS, and a DESCRIPTION of what happened.
Why is release history important?
With raw manifests, there is no record of what you deployed yesterday versus today. Helm keeps a versioned history of every change. If revision 2 causes problems, you can roll back to revision 1 with a single command.
Only seeing 1 revision?
Make sure you ran helm upgrade (not helm install). An upgrade creates a new revision. Running install again would fail because the release already exists.
help me understand my Helm release history
You have deployed, verified, and upgraded your app without editing a single YAML file after the initial chart creation. Helm handled the templating, the deployment, and the version tracking for you.
Your app is running with 4 replicas and you have a full release history. Next up, you will test what happens when a deployment goes wrong and prove that Helm can save you with a one-command rollback.
Upgrade Gone Wrong
Your Helm chart is deployed, configurable, and versioned. You can scale your app with a single --set flag and see every change recorded in your release history.
But what happens when a deployment goes wrong? In production, bad releases are inevitable. Someone pushes a broken image, a typo sneaks into a config, or a dependency disappears. Without Helm, recovering means manually figuring out what changed across multiple files and reapplying the old versions. With Helm, you roll back to any previous revision in one command.
In this step, get ready to:
- Ship a bad upgrade that breaks your running app.
- Recover instantly with a single rollback command.
- Inspect your full release history to see every revision recorded.
Ship a bad upgrade (intentionally)
Right now your app is healthy. Four pods are running nginx:stable and serving traffic on port 30080. You are going to break it on purpose by deploying an image tag that does not exist.
- Deploy a bad image tag by running this command:
helm upgrade my-webapp ./webapp-chart --set image.tag=nonexistent-v99
What does this command do?
This tells Helm to upgrade your my-webapp release with a new image tag value. Kubernetes will try to pull nginx:nonexistent-v99 from Docker Hub. That tag does not exist, so the pull will fail.
Before you check on your pods, take a moment to predict what you will see. Do you think the pods will be Running, or will Kubernetes report an error?
- Check the status of your pods by running:
kubectl get pods
You will see pods stuck in ImagePullBackOff or ErrImagePull. Kubernetes cannot find the image tag you specified, so the containers cannot start.
Your app is broken. Without Helm, you would need to figure out exactly what changed, open the correct manifest files, revert the values by hand, and reapply. That is the gap Helm closes.
This failure is intentional
You deployed a nonexistent image tag on purpose to simulate a bad production release. The ImagePullBackOff status is exactly what you should see. Do not try to fix it manually.
Roll back with one command
Helm tracks every revision of your release. Revision 2 was the last working state (4 replicas running nginx:stable). You can jump back to it instantly.
- Roll back to revision 2 by running this command:
helm rollback my-webapp 2
What does this command do?
The first argument is the release name (my-webapp) and the second is the revision number to revert to. Helm re-applies the exact configuration from revision 2, restoring your working image tag and replica count.
If you omit the revision number, Helm rolls back to the previous release by default.
- Confirm your pods are healthy again by running:
kubectl get pods
All pods should now show Running status. The broken image is gone and nginx:stable is back.
- Open http://localhost:30080 in your browser to confirm the app is serving traffic again.
You should see the nginx welcome page. Your app recovered from a broken deployment with a single command.
Pods still showing errors after rollback?
Kubernetes may take 10-20 seconds to terminate the old pods and start new ones. Run kubectl get pods again after waiting a moment.
If pods are stuck in Terminating status, wait another 30 seconds. Kubernetes respects graceful shutdown periods before force-killing containers.
Still stuck? Why are my pods not recovering after helm rollback?
Inspect the full release history
Helm recorded every action you took. The history shows the initial install, the scale-up, the failed upgrade, and the rollback. This audit trail is invaluable when debugging production issues.
Before you run the next command, predict how many revisions you expect to see. Remember: you installed (1), scaled to 4 replicas (2), deployed the bad image (3), and rolled back (4).
- View the full release history by running:
helm history my-webapp
You should see 4 revisions. Each row shows its REVISION number, UPDATED timestamp, STATUS, CHART version, APP VERSION, and a DESCRIPTION of what happened at that revision.
What should I see in the history?
- Revision 1: your initial install.
- Revision 2: the scale to 4 replicas.
- Revision 3: the failed upgrade with the bad image tag.
- Revision 4: the rollback to revision 2, which restored the working state.
The DESCRIPTION column tells you what happened at each point. You will see entries like "Upgrade complete" and "Rolled back to 2". This gives you a complete audit trail without needing any external logging.
You have proven that Helm is more than a deployment tool. It is a safety net. No matter how badly a release goes, you are always one command away from a known-good state. Next up, you will take on a Secret Mission to deploy the same chart to multiple environments with different configurations.
Secret mission
Multi-Environment Deployment
Real teams don't deploy the same configuration everywhere. Can you deploy your chart to both a dev and prod environment simultaneously, each with its own settings?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on your local machine using Docker Desktop Kubernetes. There are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Helm releases running on your local Kubernetes cluster (dev-webapp and prod-webapp, or my-webapp if you skipped the Secret Mission).
- Local helm-project directory on your Desktop containing raw manifests, chart files, and values files.
- Helm CLI installed via winget.
Keep everything running
No action needed. Choose this if you are still actively experimenting with your Helm chart or want to try deploying different configurations.
- Your Helm releases continue running on Docker Desktop Kubernetes at no cost.
- Your project files stay in the helm-project folder on your Desktop.
- The Helm CLI remains installed and available in your terminal.
Pause - I'll come back to this later
Shut down Docker Desktop to free up system memory. Your Kubernetes resources will resume automatically when you restart Docker Desktop.
- Right-click the Docker Desktop icon in your system tray and select Quit Docker Desktop.
- Your Helm releases and pods will stop, but they will start again when you reopen Docker Desktop.
- Your project files remain on your Desktop ready for next time.
Delete - I don't want to use this again
Remove all project resources and start fresh. This removes your Helm releases, Kubernetes resources, project files, and the Helm CLI.
- Uninstall your Helm releases by running these commands in your terminal:
helm uninstall dev-webapp
helm uninstall prod-webapp
Skipped the Secret Mission?
If you did not complete the Secret Mission, run helm uninstall my-webapp instead.
- Confirm all releases are gone by running:
helm list
You should see an empty list with no releases.
- Delete any leftover raw manifest resources (if they were not already removed in Step 4) by running:
kubectl delete -f raw-manifests/
- Delete your project directory by running:
cd ~\Desktop
rmdir /s /q helm-project
- Uninstall the Helm CLI by running:
winget uninstall Helm.Helm
Once complete, Helm and all project resources are fully removed from your machine.
Nice Work!
Nice Work!
You just built a production-style Helm chart from scratch and used it to deploy, upgrade, and recover a web application on Kubernetes.
You've learned how to:
- Built a Helm chart from scratch with parameterized templates, turning rigid YAML manifests into a reusable package you can configure with a single values.yaml file.
- Deployed, upgraded, and customized your app using Helm commands instead of manually editing YAML files across multiple resources.
- Recovered from a broken deployment with a single rollback command, proving you can manage releases safely without scrambling to revert files by hand.
- Secret Mission: Deployed the same chart to multiple environments with different configurations using values files.
Ready to quiz yourself?