Helm for Beginners

Helm Charts Anatomy

Writing a Helm chart

In this guide, you'll learn how to build a simple Helm chart from scratch. We will demonstrate how Helm templates work to create unique and configurable Kubernetes resource names. Helm charts are extremely versatile—they not only automate the installation of Kubernetes packages but also perform additional tasks (like backing up a database before upgrades) much like installation wizards on traditional operating systems.

For example, consider an upgrade command such as:

$ helm upgrade wordpress-release bitnami/wordpress

While this may seem complex at first, we will begin with a basic example and progressively introduce more advanced concepts.

Creating a Simple "Hello World" Chart

In this section, we will create a Helm chart for a simple "Hello World" application. The application will use an Nginx deployment with two replicas and expose the service through a NodePort.

Below are the Kubernetes manifest files for our Hello World application:

# Service definition for hello-world
apiVersion: v1
kind: Service
metadata:
  name: hello-world
spec:
  type: NodePort
  ports:
    - port: 80
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app: hello-world
# Deployment definition for hello-world
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-world
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
        - name: hello-world
          image: nginx
          ports:
            - name: http
              containerPort: 80
              protocol: TCP

Helm charts adhere to a specific directory structure that typically includes the templates/ folder along with files such as Chart.yaml, values.yaml, LICENSE, and README.md.

Using Helm to Scaffold the Chart

You do not need to manually create this structure. The Helm CLI can generate a skeleton chart for you:

$ helm create nginx-chart
$ ls nginx-chart
charts  Chart.yaml  templates  values.yaml

Now, you can replace or add your own Kubernetes manifest files (for example, the deployment and service files shown above) into the templates/ directory. Initially, the generated Chart.yaml file contains default data based on the provided chart name.

Examining and Modifying Chart Metadata

At this stage, you might want to update the Chart.yaml file to include more detailed metadata. For instance, if your company is developing this chart for internal use, you can update the file with a more descriptive summary and add maintainer contacts.

To open and edit the file:

$ cd nginx-chart
$ vi Chart.yaml

The original content may resemble this:

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

Modify it to add details and contact information:

apiVersion: v2
name: nginx-chart
description: Basic nginx website
type: application
version: 0.1.0
appVersion: "1.16.0"
maintainers:
  - name: john smith
    email: [email protected]

Once you have updated the metadata, remove any unnecessary sample template files from the templates/ directory:

$ cd nginx-chart
$ ls templates
deployment.yaml  _helpers.tpl  hpa.yaml  ingress.yaml  NOTES.txt  serviceaccount.yaml  service.yaml  tests

For your simple application, add your custom deployment and service YAML files to this folder, and your chart will be ready for installation.

Static vs. Templated Names

When you install a Helm chart, the objects in the templates are created exactly as defined. For example:

$ helm install hello-world-1 ./nginx-chart
$ kubectl get deployment
NAME          READY   UP-TO-DATE   AVAILABLE   AGE
hello-world   0/2     2            0           24s

Since the deployment name is hardcoded as hello-world, installing another release of the same chart leads to name conflicts:

$ helm install hello-world-2 ./nginx-chart
Error: rendered manifests contain a resource that
already exists. Unable to continue with install:
Deployment "hello-world" in namespace "default" exists
and cannot be imported into the current release:
invalid ownership metadata; annotation validation
error: key "meta.helm.sh/release-name" must equal "hello-world-2"; current value is "hello-world-1"

To avoid conflicts, leverage Helm's templating language to create dynamic names based on the release name. For instance, update your service and deployment definitions as follows:

# templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ .Release.Name }}-svc
spec:
  type: NodePort
  ports:
    - port: 80
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app: hello-world
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-nginx
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: hello-world
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
        - name: hello-world
          image: "{{ .Values.image }}"
          ports:
            - name: http
              containerPort: 80
              protocol: TCP

Now, when you install the chart using different release names, Helm replaces the template directives (e.g., {{ .Release.Name }}) with your specified release name:

$ helm install hello-world-1 ./nginx-chart
$ helm install hello-world-2 ./nginx-chart
$ kubectl get deployment
NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
hello-world-1-nginx     1/2     2            1           8s
hello-world-2-nginx     0/2     2            0           4s

Tip

Basing resource names on the Helm release name ensures that multiple installations in the same namespace do not conflict.

Exposing Configurable Values

Customization is key for production-ready charts. Often, you might want to configure deployment attributes—such as the number of replicas or the container image—to suit different environments. The values.yaml file serves this purpose by storing default configurations that your templates can reference.

Consider the following simple values.yaml:

# Default values for nginx-chart.
replicaCount: 2
image: nginx

Your deployment template then references these values:

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-nginx
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: hello-world
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
        - name: hello-world
          image: "{{ .Values.image }}"
          ports:
            - name: http
              containerPort: 80
              protocol: TCP

This setup allows users to override default settings during installation, for example:

$ helm install hello-world-1 ./nginx-chart \
    --set replicaCount=3 \
    --set image=nginx

For more intricate configurations, you can structure the values file as a dictionary. For example, separate the image details:

# Default values for nginx-chart.
replicaCount: 2
image:
  repository: nginx
  pullPolicy: IfNotPresent
  tag: "1.16.0"

And update your deployment template accordingly:

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-nginx
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: hello-world
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
        - name: hello-world
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 80
              protocol: TCP

This approach constructs the complete container image string dynamically and allows you to adjust the image pull policy as needed.

Summary

When you install a Helm chart, Helm processes the templates in your templates/ directory together with several sources of configuration:

• Release-specific details (such as release name, namespace, and revision)
• Default values defined in values.yaml
• Metadata from Chart.yaml
• Information from your Kubernetes cluster

The resulting manifest files are then used by Kubernetes to deploy your resources. By designing templates with dynamic naming (using {{ .Release.Name }}) and configurable values (via values.yaml), you guarantee that each chart installation creates uniquely named objects and can be tailored easily by users.

Happy templating, and see you in the next lesson!


For more information on Helm, visit the Helm Documentation.

Watch Video

Watch video content

Previous
Understanding Helm charts