Helm charts are essentially templated Kubernetes resource definitions that allow you to package, deploy, and manage complex applications.

Let’s see it in action. Imagine we’re deploying a basic Nginx web server.

First, we need a Helm chart. We can create a simple one:

helm create my-nginx
cd my-nginx

This creates a directory structure:

my-nginx/
├── charts/
├── charts.lock
├── .helmignore
├── Chart.yaml
├── ignore/.helmignore
├── templates/
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── service.yaml
│   └── serviceaccount.yaml
└── values.yaml

The Chart.yaml file contains metadata about the chart:

apiVersion: v2
name: my-nginx
description: A Helm chart for Nginx
type: application
version: 0.1.0
appVersion: "1.16.0"

The values.yaml file is where we define configurable parameters for our deployment:

replicaCount: 1

image:
  repository: nginx
  pullPolicy: IfNotPresent
  tag: ""

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

serviceAccount:
  create: true
  annotations: {}
  name: ""

podAnnotations: {}
podLabels: {}

podSecurityContext: {}
securityContext: {}

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false
  className: ""
  annotations: {}
  hosts:
    - host: chart-example.local
      paths:
        - path: /
          pathType: ImplementationSpecific
  tls: []

resources: {}

autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 100
  targetCPUUtilizationPercentage: 80
  targetMemoryUtilizationPercentage: 80

nodeSelector: {}
tolerations: []
affinity: {}

The templates/ directory contains the Kubernetes manifest files that Helm will render. For example, templates/deployment.yaml:


{{- with .Values.image }}

apiVersion: apps/v1
kind: Deployment
metadata:

  name: {{ include "my-nginx.fullname" . }}

  labels:

    {{- include "my-nginx.labels" . | nindent 4 }}

spec:

  replicas: {{ .Values.replicaCount }}

  selector:
    matchLabels:

      {{- include "my-nginx.selectorLabels" . | nindent 6 }}

  template:
    metadata:

      {{- with .Values.podAnnotations }}

      annotations:

        {{- toYaml . | nindent 8 }}


      {{- end }}

      labels:

        {{- include "my-nginx.selectorLabels" . | nindent 8 }}

    spec:

      {{- with .Values.imagePullSecrets }}

      imagePullSecrets:

        {{- toYaml . | nindent 8 }}


      {{- end }}


      serviceAccountName: {{ include "my-nginx.serviceAccountName" . }}

      containers:

        - name: {{ .Chart.Name }}


          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"


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

          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:

            {{- toYaml .Values.resources | nindent 12 }}


      {{- with .Values.nodeSelector }}

      nodeSelector:

        {{- toYaml . | nindent 8 }}


      {{- end }}


      {{- with .Values.affinity }}

      affinity:

        {{- toYaml . | nindent 8 }}


      {{- end }}


      {{- with .Values.tolerations }}

      tolerations:

        {{- toYaml . | nindent 8 }}


      {{- end }}


{{- end }}

Notice the {{ ... }} syntax. This is Go templating. Helm processes these templates, substituting values from values.yaml (and potentially overrides) to generate the final Kubernetes manifests.

To deploy this chart:

helm install my-nginx ./my-nginx

Helm creates a "release," which is an instance of a chart deployed to your Kubernetes cluster. You can then manage this release:

helm list
helm upgrade my-nginx ./my-nginx --set replicaCount=3
helm uninstall my-nginx

The true power of Helm lies in its ability to abstract away the complexity of Kubernetes configurations. Instead of managing dozens of YAML files for a microservice architecture, you manage a single chart. This chart can include deployments, services, ingress rules, configmaps, secrets, and even custom resources. Dependencies between charts are also supported, allowing you to manage complex stacks like databases, message queues, and your application all from a single Chart.yaml.

A crucial, often overlooked, aspect of Helm’s templating is the _helpers.tpl file. This file defines named templates (partials) that are reused across your manifest files. For instance, you’ll find templates like {{ include "my-nginx.fullname" . }} and {{ include "my-nginx.labels" . }}. These are not just for generating names and labels; they encapsulate common logic for creating consistent and predictable Kubernetes object names and labels across all resources within a chart. By using these helpers, you ensure that related resources (like a Deployment and its Service) share a common naming convention and labels, which is essential for Kubernetes’ internal workings and for your own organizational clarity. This reduces duplication and makes your charts more maintainable.

The next step in mastering Helm is understanding how to create and manage dependencies between charts, allowing you to build sophisticated application stacks.

Want structured learning?

Take the full DevOps & Platform Engineering course →