Helm - The Package Manager

Sahil NaikSahil Naik
15 min read

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 app

  • service.yaml for networking

  • configmap.yaml for environment variables

  • Possibly 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 configurations

  • A 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, or helm 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

  1. Basic Helm setup & help

     # Check Helm version
     helm version
    
     # Get general help
     helm help
    
     # Get help for a specific command
     helm help install
    
  1. 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
    
  2. 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
    
  3. 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
    
  4. 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
    
  5. 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:

  1. greet-service β†’ Provides a greeting message on /greet and exposes a Prometheus /metrics endpoint.

  2. 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 like Hello, 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:

  1. 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 β†’ returns Hello, <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.

  1. 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:

  1. 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
  1. 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 and Service YAML files for each microservice (greet-service and user-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 ManifestHelm 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:

  1. 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 from values.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 from values.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 from values.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 and user-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:


✨ Ending note:

In this blog, we’ve journeyed through the world of Helm charts, using our greet-service and user-servicemicroservices 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!

Thank you again, and happy coding! πŸš€


10
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. πŸš€