Helm - The Package Manager


If youβve ever worked with Kubernetes, you probably know how powerful it isβbut also how overwhelming it can get. Managing multiple YAML files, repeating configurations for each environment, and keeping track of every little setting can quickly become a nightmare. Even deploying a simple application often means writing pages of boilerplate code.
Thatβs where Helm comes in. In this blog, weβre going to dive into what Helm is and how it helps DevOps engineers cut down on repetitive YAML files and streamline Kubernetes deployments.
What is Helm?
Helm is a tool that helps you manage applications on Kubernetesβkind of like a package manager (like apt
for Ubuntu), but for Kubernetes.
When you deploy an app on Kubernetes, you usually have to write a lot of YAML files: deployments, services, secrets, etc. Itβs time-consuming, repetitive, and easy to mess up.
Helm makes this easier by:
π¦ Packaging everything into one unit called a Helm Chart
π§© Letting you use variables and templates, so you donβt repeat the same YAML over and over
βοΈ Allowing you to install, upgrade, and uninstall apps easily with just a few commands
Life With and Without Helm
π§βπ» Without Helm (Manual Kubernetes Deployment):
You write raw Kubernetes YAML files for every component:
deployment.yaml
for the appservice.yaml
for networkingconfigmap.yaml
for environment variablesPossibly secrets, ingress, and more...
Every time you want to deploy the app in a different environment (like staging or production), you end up copying and editing the same files with just a few changesβlike image version, number of replicas, or resource limits. This is repetitive, error-prone, and hard to manage.
π οΈ With Helm (Templated Deployment):
Instead of writing plain YAML, you use a Helm Chart, which is a structured folder that contains:
Templates (
.yaml
files with placeholders like{{ .Values.image.tag }}
)A
values.yaml
file to set your custom configurationsA
Chart.yaml
to define metadata (name, version, etc.)
Now, you can:
Use the same chart in multiple environments
Just change values in the
values.yaml
file (no need to rewrite YAML)Run commands like
helm install
,helm upgrade
, orhelm uninstall
to manage your app
π§± Helm Components
When using Helm, there are a few key components weβll work with that makes Helm powerful and flexible for managing Kubernetes applications.
1. π§© Chart
A Helm Chart is like a packaged applicationβit contains everything needed to deploy an app on Kubernetes.
Whatβs inside:
Templated YAML files
Default configuration (
values.yaml
)Metadata (
Chart.yaml
)
Example:
A chart for NGINX might include a deployment template, service template, and a default config that says βuse image nginx:1.25β.
2. π Values File (values.yaml
)
A file where you define the values that replace variables in your chart templates.
Why itβs useful:
You can change just a few lines in this file to deploy your app in different environments (like dev, staging, or prod), without touching the templates.Example:
replicaCount: 2 appName: myapp image: repository: nginx tag: "1.21"
3. π§ Templates
YAML files written with placeholders (using Go template syntax like {{ .Values.replicaCount }}
) that get filled with actual values from values.yaml
.
Why itβs useful:
Templates make your deployment flexible and reusable. You donβt hardcode valuesβyou let Helm insert them.Example (template line):
apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.appName }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app: {{ .Values.appName }} template: metadata: labels: app: {{ .Values.appName }} spec: containers: - name: {{ .Values.appName }} image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
4. πͺͺ Chart.yaml
In a Helm chart, Chart.yaml
is not where you put deployment values β itβs the metadata file that describes the chart itself.
Why itβs useful:
Helm uses this to identify the chart and manage its versioning.Example:
apiVersion: v2 name: myapp-chart description: A Helm chart for deploying MyApp version: 1.0.0 # Helm chart version appVersion: "1.21" # Version of the application being deployed maintainers: - name: Alice DevOps email: alice@example.com
5. π§° Release
A release is a running instance of a chart in your Kubernetes cluster.
Why itβs useful:
You can have multiple releases of the same chart (e.g., one for dev, one for prod) with different values.Example:
When you run:helm install my-nginx bitnami/nginx
β
my-nginx
is your release name.
6. πͺ Repository
A Helm repository is a place where charts are stored and sharedβlike npm
for JavaScript or PyPI
for Python.
Why itβs useful:
You can install popular charts from public repos like Bitnami, or create private ones for your organization.Example:
helm repo add bitnami https://charts.bitnami.com/bitnami
π Helm Commands
Basic Helm setup & help
# Check Helm version helm version # Get general help helm help # Get help for a specific command helm help install
Repository management
# Add a Helm repository helm repo add <repo-name> <repo-url> helm repo add bitnami https://charts.bitnami.com/bitnami # Update local repo cache helm repo update # List all added repos helm repo list # Search for charts in repos helm search repo <keyword> helm search repo nginx
Chart development / local work
# Create a new chart scaffold helm create <chart-name> helm create mychart # Lint (check) the chart for errors helm lint <chart-directory> helm lint mychart # Render templates locally (no apply) helm template <release-name> <chart-directory> -f values.yaml helm template myrelease ./mychart -f values.yaml # Package your chart helm package <chart-directory> helm package mychart # Show chart info or values from a packaged chart helm show chart <chart-file> helm show chart mychart-1.0.0.tgz helm show values <chart-file> helm show values mychart-1.0.0.tgz
Installing and managing releases
# Install a release helm install <release-name> <chart> -f values.yaml helm install myrelease ./mychart -f values.yaml # Upgrade a release helm upgrade <release-name> <chart> -f values.yaml helm upgrade myrelease ./mychart -f values.yaml # Upgrade or install if missing helm upgrade --install <release-name> <chart> -f values.yaml helm upgrade --install myrelease ./mychart -f values.yaml # Uninstall (delete) a release helm uninstall <release-name> helm uninstall myrelease # List all installed releases helm list # Get the status of a release helm status <release-name> helm status myrelease # Dry-run install or upgrade (simulate, no apply) helm install --dry-run --debug <release-name> <chart> helm install --dry-run --debug myrelease ./mychart
Rollback and history
# See the release history helm history <release-name> helm history myrelease # Roll back to a specific revision helm rollback <release-name> <revision> helm rollback myrelease 1 # Roll back to the last revision helm rollback <release-name> helm rollback myrelease
Managing dependencies (if using sub-charts)
# Update chart dependencies helm dependency update <chart-directory> helm dependency update mychart # Build chart dependencies (if charts/ directory is used) helm dependency build <chart-directory> helm dependency build mychart
π§π»βπ» Helm Microservice Project:
π‘ Project Overview
The project consists of two Go microservices:
greet-service β Provides a greeting message on
/greet
and exposes a Prometheus/metrics
endpoint.user-service β Provides user details on
/user
and also exposes a Prometheus/metrics
endpoint.
Together, these services demonstrate:
β
Basic REST APIs
β
Metrics integration
β
Independent deployment
β
Separation of concerns
π What does the project do?
greet-service
Exposes an HTTP API at
/greet
that returns a greeting likeHello, welcome to our system!
Provides a
/metrics
endpoint to expose Prometheus-compatible metrics (useful for monitoring).
user-service
Exposes an HTTP API at
/user
that returns user information like name, age, or email.Provides its own
/metrics
endpoint for Prometheus.
Both services are:
β
Lightweight
β
Built in Go
β
Dockerized
β
Ready to be orchestrated with Kubernetes and Helm
π§π»βπ» The Project:
greet-service:
package main import ( "fmt" "log" "net/http" "github.com/prometheus/client_golang/prometheus/promhttp" ) func main() { http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(`{"status":"ok"}`)) }) http.HandleFunc("/greet", func(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") if name == "" { name = "World" } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(fmt.Sprintf(`{"message":"Hello, %s!"}`, name))) }) // Prometheus metrics endpoint http.Handle("/metrics", promhttp.Handler()) port := "8080" log.Printf("Starting greet-service on port %s", port) log.Fatal(http.ListenAndServe(":"+port, nil)) }
The greet-service is a lightweight Go microservice that returns a greeting message and exposes a health check and Prometheus metrics.
β What Weβve Done:
Built a Go app with three endpoints:
/greet
β returnsHello, <name>!
as JSON/health
β standard health check for readiness/liveness/metrics
β exposes Prometheus metrics
Dockerized the service
Deployed it to Kubernetes using a Helm chart
Made it production-ready with observability in mind
This forms one half of our Helm-based microservice project.
user-service:
package main import ( "encoding/json" "fmt" "log" "net/http" "strings" "github.com/prometheus/client_golang/prometheus/promhttp" ) type User struct { ID string `json:"id"` Name string `json:"name"` Age int `json:"age"` } func main() { http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(`{"status":"ok"}`)) }) http.HandleFunc("/user/", func(w http.ResponseWriter, r *http.Request) { id := strings.TrimPrefix(r.URL.Path, "/user/") if id == "" { http.Error(w, "Missing user ID", http.StatusBadRequest) return } // Mock user data user := User{ ID: id, Name: fmt.Sprintf("User %s", id), Age: 25, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(user) }) // Prometheus metrics endpoint http.Handle("/metrics", promhttp.Handler()) port := "8081" log.Printf("Starting user-service on port %s", port) log.Fatal(http.ListenAndServe(":"+port, nil)) }
The user-service is a simple Go microservice that provides user information based on an ID and also exposes health and metrics endpoints.
β What Weβve Done:
Developed a Go app with three endpoints:
/user/{id}
β returns mock user details as JSON (ID, name, age)/health
β health check endpoint for readiness/liveness/metrics
β exposes Prometheus metrics for monitoring
Returns dynamically generated mock data for any user ID
Dockerized and deployed to Kubernetes using Helm
Integrated observability with Prometheus
This service works alongside the greet-service as part of a Helm-based microservice project for learning deployment, monitoring, and scaling in Kubernetes.
π Traditional Kubernetes manifest!
Initially, we created the Kubernetes manifest for both the services as usual i.e deployment.yaml and service.yaml which is as follows:
greet-service:
// deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: greet-service
labels:
app: greet-service
spec:
replicas: 1
selector:
matchLabels:
app: greet-service
template:
metadata:
labels:
app: greet-service
spec:
containers:
- name: greet-service
image: greet-service:latest
ports:
- containerPort: 8080
---
// service.yaml
apiVersion: v1
kind: Service
metadata:
name: greet-service
spec:
selector:
app: greet-service
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
user-service:
// deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
labels:
app: user-service
spec:
replicas: 1
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: user-service:latest
ports:
- containerPort: 8081
---
// service.yaml
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
selector:
app: user-service
ports:
- protocol: TCP
port: 80
targetPort: 8081
type: ClusterIP
In the traditional setup:
We manually wrote
Deployment
andService
YAML files for each microservice (greet-service
anduser-service
).Each file contained hardcoded values for:
Service names
Container image names
Ports
Labels and selectors
To apply these, we ran:
kubectl apply -f <file>.yaml
While this works, it becomes repetitive, hard to maintain, and error-prone as your project scales or moves across environments (dev/staging/prod).
π¦ What We Will Do with Helm Instead
Helm helps us template and parameterize these manifests, making them reusable and easier to manage.
β With Helm:
We replace hardcoded values with variables (e.g.,
{{ .Values.image.repository }}
)These variables are defined in a separate config file:
values.yaml
We package all manifests and configs into a Helm chart
π Benefits of Using Helm
β Reusability: One template, many deployments (just change
values.yaml
)β Separation of config and logic: Code (YAML) is clean; config is flexible
β Versioning and packaging: Helm lets you version and share charts
β Rollbacks: Helm makes it easy to roll back to previous versions
β Environment-specific configs: You can override values at install time
ποΈ Helm Chart Structure Weβll Create
For each service (like greet-service
), weβll create:
greet-service/
βββ templates/ # Kubernetes YAML templates
β βββ deployment.yaml # Template for Deployment
β βββ service.yaml # Template for Service
βββ Chart.yaml # Metadata about the chart (name, version, description)
βββ values.yaml # Default values injected into the templates
π What Goes Inside These Files?
β
Chart.yaml
This file holds the basic metadata about your Helm chart β like the name of the microservice, the version of the chart, and a short description. Itβs like a chartβs identity card.
β
values.yaml
This is where we define all the configurable values (like image name, tag, ports, replicas). Instead of hardcoding these into our deployment files, we put them here so they can be easily changed for different environments.
β
templates/deployment.yaml
This is a template version of the Kubernetes Deployment file. Instead of fixed values, it uses variables (from values.yaml
). Helm will fill in the correct values when we install the chart.
β
templates/service.yaml
Just like the deployment template, this is the template version of the Service file. It also uses placeholders for values like port and service type, which Helm replaces during deployment.
What We Replace in YAML
Traditional Manifest | Helm Template Equivalent |
image: greet-service:latest | {{ .Values.image.repository }}:{{ .Values.image.tag }} |
replicas: 1 | {{ .Values.replicaCount }} |
port: 8080 | {{ .Values.service.targetPort }} |
name: greet-service | {{ . Chart.Name }} or from Values |
π₯ Enterβs Helm:
greet-service:
Kubernetes manifest transforms into:
// deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Chart.Name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Chart.Name }}
template:
metadata:
labels:
app: {{ .Chart.Name }}
spec:
containers:
- name: {{ .Chart.Name }}
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.port }}
---
// service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ .Chart.Name }}
spec:
type: {{ .Values.service.type }}
selector:
app: {{ .Chart.Name }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.port }}
π deployment.yaml
(Helm Template)
This file is a Helm-templated Kubernetes Deployment.
We use placeholders (
{{ }}
) to inject values fromvalues.yaml
instead of hardcoding them.{{ .Chart.Name }}
sets the app name dynamically based on the chart name.{{ .Values.replicaCount }}
controls how many pods to run β this comes fromvalues.yaml
.The image, pull policy, and container port are also pulled from
values.yaml
, making this config flexible and reusable.
π§ Goal: Create a reusable, parameterized Deployment that works across environments.
π service.yaml
(Helm Template)
This file defines a Helm-templated Kubernetes Service.
Again, we use
{{ .Chart.Name }}
to name the service and match it with the Deployment.The service type (
ClusterIP
,NodePort
, etc.) and port are injected fromvalues.yaml
.It connects to the correct pods using the
app
label.
π§ Goal: Expose the Deployment via a Service using values we can change easily.
// values.yaml
replicaCount: 1
image:
repository: nsahil992/greet-service
tag: latest
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 8080
resources: {}
---
// Chart.yaml
apiVersion: v2
name: greet-service
description: A Helm Chart for the Go greet microservice
type: application
version: 0.1.0
appVersion: "1.0"
π values.yaml
This file defines the configurable values used in the chart:
replicaCount
: Number of pod replicas to run.image
: Details about the container image (repository name, tag, and pull policy).service
: Type of service (e.g.,ClusterIP
) and the port to expose.resources
: Left empty here, but can be used to define CPU/memory limits if needed.
π§ How it helps:
Instead of hardcoding values in deployment.yaml
and service.yaml
, we refer to them like {{ .Values.image.repository }}
β this makes our templates reusable, flexible, and environment-friendly (e.g., dev/stage/prod).
π Chart.yaml
This is the metadata file for the Helm chart:
name
: Name of the chart (and usually the app).description
: Short explanation of what the chart is.type
: Defines this as an application (vs library).version
: Version of the chart (for tracking changes to the Helm chart).appVersion
: Version of the actual application (e.g., your Go service).
π§ How it helps:
{{ .Chart.Name }}
in the templates picks up this name for labels and naming resources.Helps Helm identify, version, and manage this chart properly during install/upgrade.
π¦ Packaging Helm Charts
Once youβve written your Helm chart (with Chart.yaml
, values.yaml
, templates/
, etc.), the chart is just a folder.
To distribute or share it (e.g., upload to a Helm repo), you need to package it.
β Step to Package
helm package greet-service/
This command compresses the entire greet-service
chart directory into a .tgz
file:
greet-service-0.1.0.tgz
This .tgz
file contains everything needed to install the chart.
π Why: This makes the chart portable and ready for publishing to a Helm repository or Artifact Hub.
βοΈ Uploading Charts to Artifact Hub
Now that we have greet-service-0.10.tgz
, let's upload it to Artifact Hub β the public registry for Helm charts.
π Step-by-Step:
1. Create a GitHub repository
Create a public GitHub repo β for example:
https://github.com/your-username/helm-microservices
Put the .tgz
file(s) inside a root directory.
mv greet-service-0.1.0.tgz
2. Generate index.yaml
This tells Helm what charts exist and their metadata.
helm repo index . --url https://your-username.github.io/helm-microservices/charts
Now root directory will contain:
index.yaml
greet-service-0.1.0.tgz
3. Push to GitHub Pages
Make sure your repo is published via GitHub Pages from the root.
4. Add Repo to Artifact Hub
Go to Artifact Hub:
Create an account and link your GitHub
Register a new repository
Type: Helm
Repository name: e.g.,
helm-microservices
URL:
https://your-username.github.io/helm-microservices/charts
Wait for it to index
β Result
We can now install your chart like:
helm repo add my-microservices https://your-username.github.io/helm-microservices/charts
helm install greet-release my-microservices/greet-service
π In Context of Our Project
Weβll package
greet-service
anduser-service
charts.Host them on GitHub Pages.
Index them with
index.yaml
.Share via Artifact Hub for others to install easily.
This process shows you how to build, secure, and publish Helm charts professionally!
π Resources:
GitHub Repository:
Artifact Hub:
β¨ Ending note:
In this blog, weβve journeyed through the world of Helm charts, using our greet-service
and user-service
microservices as examples. Weβve covered how Helm simplifies Kubernetes deployments by allowing us to package, configure, and manage resources with reusable, customizable templates.
From understanding the benefits of Helm over traditional Kubernetes manifests to diving deep into chart packaging, signing, and uploading to Artifact Hub β we now have the tools to efficiently manage our Kubernetes applications with Helm.
By leveraging Helm's powerful templating, versioning, and repository management features, weβve built a scalable and reusable system that can be easily shared, maintained, and deployed across different environments. This approach helps us save time, reduce errors, and ensure consistency in our deployments.
Whether we're building small projects or scaling complex applications, Helm is an essential tool for Kubernetes management. With the steps outlined in this blog, we should be ready to start applying Helm to your own projects β bringing flexibility and efficiency to our Kubernetes workflows.
π€πΌ Connect:
If you've made it this far, I truly appreciate you taking the time to read through this blog! I hope you found the content useful and that it helps you in your future projects. If you enjoyed the post, feel free to connect with me on GitHub or LinkedIn. Don't forget to subscribe to my newsletter to receive my latest blogs directly in your inbox!
GitHub: https://github.com/nsahil992
LinkedIn: https://www.linkedin.com/in/nsahil992
Thank you again, and happy coding! π
Subscribe to my newsletter
Read articles from Sahil Naik directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Sahil Naik
Sahil Naik
π» Sahil learns, codes, and automates, documenting his journey every step of the way. π