# Understanding Helm Charts in Kubernetes

When working with [Kubernetes](https://betterstack.com/community/guides/scaling-docker/kubernetes-getting-started/), managing application
deployments can quickly become complex. As applications grow, so does the number
of configuration files needed to deploy them.

[Helm](https://helm.sh/), often described as the package manager for Kubernetes,
offers an elegant solution to this challenge through the concept of Helm charts.

In this guide, we'll dive deep into what Helm charts are, how they work, and how
you can use them to simplify your Kubernetes deployments.

[ad-logs]

## What are Helm charts?

Helm charts are packages of pre-configured Kubernetes resources that can be
managed as a single unit. Think of them as the equivalent of a software package
in traditional package managers like `apt`, `yum`, or `npm`, but specifically
designed for Kubernetes applications.

A Helm chart contains all the resource definitions necessary to run an
application in a Kubernetes cluster. This includes deployments, services, config
maps, secrets, and any other Kubernetes resources your application might need.
By bundling these resources together, Helm charts make it significantly easier
to deploy and manage complex applications on Kubernetes.

```text
mychart/
  Chart.yaml          # Contains chart metadata and information
  values.yaml         # Default configuration values
  templates/          # Directory containing templated YAML files
    deployment.yaml
    service.yaml
  charts/             # Directory for dependent charts
  LICENSE             # Optional: Contains the chart license
  README.md           # Optional: Documentation
```

## The problem Helm charts solve

To understand why Helm charts are valuable, consider this common scenario:

You have a microservice-based application consisting of several components, each
requiring multiple Kubernetes resources (deployments, services, config maps,
etc.). For each environment (development, staging, production), these resources
need slight variations in configuration.

Without Helm, you would:

1. Maintain separate YAML files for each resource
2. Manually track which files go together
3. Create different versions of these files for each environment
4. Manually handle updates and rollbacks

This approach becomes unwieldy as your application grows. Helm charts solve this
problem by:

1. Bundling related resources together
2. Providing templating capabilities for configuration
3. Managing versioning and release history
4. Simplifying updates and rollbacks

## Core concepts of Helm charts

Before diving deeper into Helm charts, let's understand some core concepts:

- **Chart**: A package containing all the resource definitions needed to run an
  application on Kubernetes.

- **Release**: An instance of a chart running in a Kubernetes cluster. When you
  install a chart, Helm creates a new release. Each release has a unique name.

- **Repository**: A place where charts can be stored and shared. Similar to
  Docker Hub for Docker images.

- **Values**: Configuration options for a chart that can be overridden during
  installation or upgrade.

## Anatomy of a Helm chart

Let's look at the key components of a Helm chart:

### Chart.yaml

This file contains metadata about the chart, such as its name, version,
description, and dependencies.

```yaml
[label Chart.yaml]
apiVersion: v2
name: my-application
version: 1.0.0
appVersion: "1.16.0"
description: A Helm chart for my application
dependencies:
  - name: mongodb
    version: 10.0.0
    repository: https://charts.bitnami.com/bitnami
```

### values.yaml

This file contains default configuration values for the chart. These values can
be overridden during installation.

```yaml
[label values.yaml]
replicaCount: 2
image:
  repository: nginx
  tag: 1.19.0
  pullPolicy: IfNotPresent
service:
  type: ClusterIP
  port: 80
resources:
  limits:
    cpu: 100m
    memory: 128Mi
  requests:
    cpu: 50m
    memory: 64Mi
```

### Templates directory

This directory contains template files that generate Kubernetes manifests. These
templates use values from the `values.yaml` file and can include conditional
logic and loops.

```yaml
[label templates/deployment.yaml]
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-deployment
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}
    spec:
      containers:
      - name: {{ .Chart.Name }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        ports:
        - containerPort: 80
        resources:
          {{- toYaml .Values.resources | nindent 12 }}
```

In this template, expressions like `{{ .Values.replicaCount }}` will be replaced
with values from the values.yaml file. The `.Release.Name` refers to the name
given when installing the chart.

## Creating your first Helm chart

Let's create a simple Helm chart for a web application. Before you proceed,
ensure that you have [helm installed](https://helm.sh/docs/intro/install/).

```command
helm create my-webapp
```

This command generates a chart with the default structure:

![my-webapp](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/9e28181c-8886-40c9-8945-367a38054d00/public =1382x594)

Edit the `Chart.yaml` file to include your application details:

```yaml
[label Chart.yaml]
apiVersion: v2
name: my-webapp
version: 0.1.0
appVersion: "1.0.0"
description: A simple web application Helm chart
```

Then modify the `values.yaml` file with your application's configuration:

```text
[label values.yaml]
replicaCount: 1

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

service:
  type: ClusterIP
  port: 80

resources:
  limits:
    cpu: 100m
    memory: 128Mi
  requests:
    cpu: 50m
    memory: 64Mi
```

The default templates include `deployment.yaml`, `service.yaml`, and others. You
can modify these or create new ones based on your requirements.

Now you can validate your chart using the lint and template commands:

```command
helm lint
```

```text
[output]
==> Linting my-webapp
[INFO] Chart.yaml: icon is recommended

1 chart(s) linted, 0 chart(s) failed
```

![Helm lint](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/c1e6c7d4-5337-4a1a-d8b3-f1fae1e37800/public =1106x387)

```command
helm template my-webapp
```

This command will output the rendered Kubernetes manifests without installing
them.

## Installing a Helm chart

Once your chart is ready, you can install it in your Kubernetes cluster:

```command
helm install my-release .
```

This command installs the chart with the release name `my-release`. You can
customize the values during installation if you wish:

```command
helm install my-release . --set replicaCount=2 --set service.type=NodePort
```

Or use a custom values file like this:

```command
helm install my-release my-webapp -f custom-values.yaml
```

![Installing Helm chart](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/4bb17490-02db-4fb6-63c8-606d5a572c00/md1x =1250x456)

## Working with Helm repositories

Helm charts can be shared through repositories. The
[Artifact Hub](https://artifacthub.io/) (previously known as the Helm Hub) is a
public repository for Helm charts.

For example, here's how to add the Bitnami application catalog:

```command
helm repo add bitnami https://charts.bitnami.com/bitnami
```

```text
[output]
"bitnami" has been added to your repositories
```

Then update your repose as follows:

```command
helm repo update
```

![Helm update repos](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/b4d639fc-e56a-4dc0-b5f2-0d6260b77100/lg1x =1238x364)

Once the repos are up-to-date, you can search for Helm charts with:

```command
helm search repo nginx
```

```text
[output]
NAME                                            CHART VERSION   APP VERSION     DESCRIPTION
bitnami/nginx                                   19.1.0          1.27.4          NGINX Open Source is a web server that can be a...
bitnami/nginx-ingress-controller                11.6.13         1.12.1          NGINX Ingress Controller is an Ingress controll...
bitnami/nginx-intel                             2.1.15          0.4.9           DEPRECATED NGINX Open Source for Intel is a lig...
prometheus-community/prometheus-nginx-exporter  1.2.0           1.4.1           A Helm chart for NGINX Prometheus Exporter
```

You will likely see a few results with hopefully the chart you're looking for.
Once you've identified it, go ahead and install it using:

```command
helm install my-nginx bitnami/nginx
```

![Installing Helm Chart](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/facadf7c-0aec-4640-6911-6a4fcbe3f100/orig =1742x1008)

## Customizing charts with templating

One of the most powerful features of Helm is its templating engine, which is
based on Go templates. This allows you to create dynamic Kubernetes manifests
that adapt to different environments and configurations.

### Basic templating

```yaml
[label templates/configmap.yaml]
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-config
data:
  app.properties: |
    environment={{ .Values.environment }}
    debug={{ .Values.debug }}
```

### Conditionals

```yaml
[label templates/service.yaml]
apiVersion: v1
kind: Service
metadata:
  name: {{ .Release.Name }}-service
spec:
  {{- if eq .Values.service.type "NodePort" }}
  type: NodePort
  {{- else if eq .Values.service.type "LoadBalancer" }}
  type: LoadBalancer
  {{- else }}
  type: ClusterIP
  {{- end }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app: {{ .Release.Name }}
```

### Loops

```yaml
[label templates/configmap.yaml]
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-config
data:
  {{- range $key, $value := .Values.configData }}
  {{ $key }}: {{ $value | quote }}
  {{- end }}
```

## Upgrading and rolling back releases

Helm makes it easy to upgrade applications and roll back to previous versions if
needed.

To upgrade to a new release, use:

```command
helm upgrade my-release my-webapp --set replicaCount=3
```

This command updates the existing release with new values.

You can also check the revision history with:

```command
helm history my-release
```

```text
[output]
REVISION    UPDATED                     STATUS      CHART            APP VERSION    DESCRIPTION
1           Tue Apr 13 10:00:00 2025    superseded  my-webapp-0.1.0  1.0.0          Install complete
2           Tue Apr 13 11:00:00 2025    deployed    my-webapp-0.1.0  1.0.0          Upgrade complete
```

Finally, you can revert to a previous version using:

```command
helm rollback my-release 1
```

This command rolls back to revision 1.

## Managing dependencies with Helm charts

Helm allows you to manage dependencies between charts, making it easier to
deploy complex applications. To define these dependencies, you'll add them to
your `Chart.yaml` file.

For example, if your application requires MongoDB, you can specify it as a
dependency by including the relevant information about the chart: its name, the
specific version you need, the repository where it can be found, and optionally
a condition that determines whether it should be installed.

Here's how you would define a MongoDB dependency:

```yaml
[label Chart.yaml]
apiVersion: v2
name: my-application
version: 1.0.0
dependencies:
  - name: mongodb
    version: 10.0.0
    repository: https://charts.bitnami.com/bitnami
    condition: mongodb.enabled
```

The `condition` field is particularly useful as it allows you to make the
dependency optional. In this example, MongoDB will only be installed if the
value `mongodb.enabled` is set to `true` in your `values.yaml` file. This gives
you flexibility to deploy your application with or without certain dependencies
based on your environment requirements.

Once you've defined your dependencies, you need to download them before
installing your chart. You can do this with the dependency update command:

```command
helm dependency update my-application
```

This command reads the dependencies from your Chart.yaml file and downloads all
the required charts to the `charts/` directory within your own chart. Each
dependency is downloaded as a packaged `.tgz` file, which will be extracted and
installed when you deploy your main chart.

When you install your chart, Helm will automatically install all these
dependencies in the correct order, handling any relationship or dependency chain
that might exist between them.

This approach to managing dependencies significantly simplifies the deployment
of complex applications, ensuring that all components are installed consistently
and with the correct configurations.

It also makes your charts more modular and reusable, as you can easily swap in
different versions of dependencies or conditionally include them based on your
deployment needs.

## Creating a real-world application chart

Let's create a more realistic example: a chart for a web application with a
Redis cache. We'll walk through the entire process step by step.

To begin, we'll initialize a new Helm chart using the `helm create` command.
This generates the standard directory structure and template files that we'll
customize for our application:

```command
helm create web-app
```

Next, we need to define our chart's dependencies. Since our application requires
Redis, we'll add it as a dependency in the Chart.yaml file. We'll specify that
we want to use version 16.0.0 of the Redis chart from the Bitnami repository,
and we'll make it conditional so it can be enabled or disabled as needed:

```yaml
[label Chart.yaml]
apiVersion: v2
name: web-app
version: 0.1.0
appVersion: "1.0.0"
description: Web application with Redis cache
dependencies:
  - name: redis
    version: 16.0.0
    repository: https://charts.bitnami.com/bitnami
    condition: redis.enabled
```

With our dependencies defined, we'll now configure the default values for our
chart in the values.yaml file. We'll set up our application to run two replicas
using a specific image.

We'll configure a ClusterIP service to expose it within the cluster on port 80,
and set up an ingress with the NGINX ingress controller. We'll also define
resource limits and requests to ensure our application gets the resources it
needs.

Finally, we'll configure the Redis dependency to use a standalone architecture
with password authentication:

```yaml
[label values.yaml]
replicaCount: 2

image:
  repository: myapp
  tag: "1.0.0"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: true
  className: "nginx"
  hosts:
    - host: myapp.local
      paths:
        - path: /
          pathType: Prefix

resources:
  limits:
    cpu: 200m
    memory: 256Mi
  requests:
    cpu: 100m
    memory: 128Mi

redis:
  enabled: true
  architecture: standalone
  auth:
    enabled: true
    password: "changeme"
```

Now we need to update the deployment template to connect our application to
Redis. We'll modify the `templates/deployment.yaml` file to include environment
variables that point to our Redis instance.

This deployment will use the values we defined earlier, such as the replica
count, image details, and resource limits.

The environment variables `REDIS_HOST` and `REDIS_PASSWORD` will allow our
application to connect to the Redis instance that will be deployed as part of
our chart:

```yaml
[label templates/deployment.yaml]
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "web-app.fullname" . }}
  labels:
    {{- include "web-app.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "web-app.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "web-app.selectorLabels" . | nindent 8 }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          env:
            - name: REDIS_HOST
              value: {{ .Release.Name }}-redis-master
            - name: REDIS_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: {{ .Release.Name }}-redis
                  key: redis-password
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
```

With our chart fully configured, we're ready to install it. First, we need to
update the dependencies to download the Redis chart:

```command
helm dependency update web-app
```

This command downloads the Redis chart and stores it in the `charts/` directory
of our `web-app` chart. Finally, we can install our chart with a single command:

```command
helm install my-web-app web-app
```

This command deploys our web application and the Redis dependency to our
Kubernetes cluster as a single release named "my-web-app".

Helm handles the creation of all the necessary Kubernetes resources, including
the deployment, service, ingress, and Redis components.

Our application is now deployed and configured to connect to Redis using the
environment variables we defined in the deployment template.

## Final thoughts

Helm charts provide a powerful way to package, configure, and deploy
applications on Kubernetes. By leveraging Helm's templating capabilities,
dependency management, and versioning features, you can significantly reduce the
complexity of managing applications in Kubernetes environments.

Whether you're deploying a simple web service or a complex microservices
architecture, Helm charts offer a standardized approach that improves
consistency, reproducibility, and maintainability across your entire application
lifecycle.

As you continue your Kubernetes journey, consider exploring the vast ecosystem
of public Helm charts available on Artifact Hub, contributing to open-source
charts, or creating your own organizational chart repository to share best
practices and standardized deployments across your teams.
