Kubernetes deployments are complex, but Helm charts are the secret sauce that makes them manageable, and creating your own is surprisingly straightforward.

Let’s get a simple web server running.

First, we need a basic Go application. Create a directory named my-web-app and inside it, a file named main.go:

package main

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello from my Helm-powered app!")
}

func main() {
	http.HandleFunc("/", handler)
	fmt.Println("Server starting on port 8080")
	http.ListenAndServe(":8080", nil)
}

Now, let’s containerize this. Create a file named Dockerfile in the same my-web-app directory:

FROM golang:1.20-alpine AS builder
WORKDIR /app
COPY main.go .
RUN go build -o my-web-app .

FROM alpine:latest
COPY --from=builder /app/my-web-app .
EXPOSE 8080
CMD ["./my-web-app"]

Build and push this image to a container registry. For this example, let’s assume you push it to Docker Hub as yourusername/my-web-app:v1.0.

Now, let’s create the Helm chart. Navigate to the directory above my-web-app and run:

helm create my-web-chart

This creates a directory structure for your chart. We’ll modify a few key files.

Open my-web-chart/Chart.yaml. This file contains metadata about your chart. Update it to look like this:

apiVersion: v2
name: my-web-chart
description: A Helm chart for my simple web app
version: 0.1.0
appVersion: "1.0"

The version is for the chart itself, and appVersion is for the application it deploys.

Next, edit my-web-chart/values.yaml. This file holds all the configurable values for your chart. We’ll define our image here:

replicaCount: 1

image:
  repository: yourusername/my-web-app
  pullPolicy: IfNotPresent
  tag: "v1.0"

service:
  type: ClusterIP
  port: 80

Now, let’s define the Kubernetes resources. Open my-web-chart/templates/deployment.yaml. This file describes how to deploy your application. We need to make sure it uses the image defined in values.yaml. Look for the spec.template.spec.containers section and modify the image field:

# ... other deployment spec ...
spec:

  replicas: {{ .Values.replicaCount }}

  template:
    metadata:
      labels:

        app: {{ include "my-web-chart.name" . }}


        version: {{ .Chart.AppVersion }}

    spec:
      containers:

        - name: {{ .Chart.Name }}


          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"


          imagePullPolicy: {{ .Values.image.pullPolicy }}

          ports:
            - name: http
              containerPort: 8080 # This must match the port your app listens on
              protocol: TCP
# ... rest of deployment.yaml ...

The {{ .Values.image.repository }} and {{ .Values.image.tag }} are Helm templating functions that pull values from values.yaml. {{ include "my-web-chart.name" . }} is a helper function generated by helm create to get the chart’s name.

Next, edit my-web-chart/templates/service.yaml. This defines how to expose your application. Update it to use the values from values.yaml:

apiVersion: v2

name: {{ include "my-web-chart.fullname" . }}

...
spec:

  type: {{ .Values.service.type }}

  ports:

    - port: {{ .Values.service.port }}

      targetPort: http # This should match the containerPort name in deployment.yaml
      protocol: TCP
      name: http
  selector:

    app: {{ include "my-web-chart.selectorLabels" . }}

The {{ .Chart.AppVersion }} template variable refers to the appVersion field in Chart.yaml. The containerPort: 8080 in deployment.yaml is crucial; it’s the port your Go application is listening on. The targetPort: http in service.yaml links the service to the container port named http.

Finally, let’s clean up any default templates we don’t need. Delete my-web-chart/templates/NOTES.txt and my-web-chart/templates/ingress.yaml (unless you plan to configure ingress).

You can test your chart locally using helm lint my-web-chart to check for syntax errors and helm template my-web-chart to see the generated Kubernetes manifests.

To deploy it to your Kubernetes cluster (ensure kubectl is configured), run:

helm install my-release ./my-web-chart

This installs your chart, creating a deployment and a service. You can then check the status with kubectl get pods and kubectl get services. To access your app, if you used ClusterIP, you might need port-forwarding: kubectl port-forward service/my-release-my-web-chart 8080:80.

The real power comes when you realize that values.yaml can define almost anything in your Kubernetes manifests, from resource limits and replicas to custom environment variables.

The next step is to manage multiple environments (dev, staging, prod) by creating different values-<env>.yaml files and using helm upgrade --values values-prod.yaml my-release ./my-web-chart.

Want structured learning?

Take the full Helm course →