A Helm chart isn’t just a collection of YAML files; it’s a declarative definition of a Kubernetes application that can be versioned, shared, and deployed repeatedly.
Let’s see this in action. Imagine you have a simple web application. Here’s how you might structure its Helm chart:
my-webapp/
├── Chart.yaml
├── values.yaml
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ └── _helpers.tpl
└── charts/
└── postgresql-ha-0.10.2/ # A dependency chart
-
Chart.yaml: This is the manifest for your chart. It contains metadata like the chart’s name, version, description, and dependencies.apiVersion: v2 name: my-webapp version: 0.1.0 description: A simple web application with a PostgreSQL dependency type: application dependencies: - name: postgresql version: "0.10.2" repository: "https://charts.bitnami.com/bitnami" -
values.yaml: This file defines the default configuration for your chart. Users can override these values during installation or upgrade.replicaCount: 1 image: repository: nginx tag: stable pullPolicy: IfNotPresent service: type: ClusterIP port: 80 ingress: enabled: true className: nginx hosts: - host: chart-example.local paths: - path: / pathType: ImplementationSpecific resources: {} # Kubernetes resource requests and limits postgresql: enabled: true auth: username: myuser password: changeme database: mydb -
templates/: This directory holds the Kubernetes manifest files (YAML) that Helm will render. These files use Go templating.-
templates/deployment.yaml: Defines the Deployment for your application.apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "my-webapp.fullname" . }} labels: {{- include "my-webapp.labels" . | nindent 4 }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: {{- include "my-webapp.selectorLabels" . | nindent 6 }} template: metadata: labels: {{- include "my-webapp.selectorLabels" . | nindent 8 }} spec: 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 resources: {{- toYaml .Values.resources | nindent 12 }} -
templates/service.yaml: Defines the Service for your application.apiVersion: v1 kind: Service metadata: name: {{ include "my-webapp.fullname" . }} labels: {{- include "my-webapp.labels" . | nindent 4 }} spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: http protocol: TCP name: http selector: {{- include "my-webapp.selectorLabels" . | nindent 4 }} -
templates/_helpers.tpl: This file contains template helpers, which are reusable snippets of template code. They help keep your main templates clean and DRY (Don’t Repeat Yourself).{{/* vim: set filetype=gotpl: */}} {{/* Expand the name of the chart. */}} {{- define "my-webapp.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} {{/* Create a default fully qualified app name. */}} {{- define "my-webapp.fullname" -}} {{- if .Values.fullnameOverride -}} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- $name := default .Chart.Name .Values.nameOverride -}} {{- if contains $name .Release.Name -}} {{- .Release.Name | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} {{- end -}} {{/* Generate labels for the chart */}} {{- define "my-webapp.labels" -}} helm.sh/chart: {{ include "my-webapp.chart" . }} {{ include "my-webapp.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end -}} {{/* Selector labels */}} {{- define "my-webapp.selectorLabels" -}} app.kubernetes.io/name: {{ include "my-webapp.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end -}} {{/* Chart name and version */}} {{- define "my-webapp.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} {{- end -}}
-
-
charts/: This directory is for subcharts, which are dependencies of your main chart. Helm will fetch and install these automatically when you deploy your chart.
The real power comes from combining these elements. You can deploy this chart with default values:
helm install my-release ./my-webapp
Or override specific values:
helm install my-release ./my-webapp \
--set replicaCount=3 \
--set image.tag=latest \
--set postgresql.auth.password=securepassword
This allows for a single chart definition to manage applications across different environments (development, staging, production) by simply changing configuration values.
The most surprising thing about Helm values is that they aren’t just simple key-value pairs; they form a hierarchical structure that directly maps to the Go templating engine, allowing for conditional logic, loops, and complex data manipulation within your Kubernetes manifests.
Consider the postgresql.enabled flag in values.yaml. If set to true, the postgresql-ha subchart will be deployed. If false, it won’t. This logic is often implemented within the _helpers.tpl file or directly in the templates referencing the subchart.
The fullnameOverride and nameOverride values in values.yaml are subtle but powerful. They allow you to completely control the generated Kubernetes resource names, decoupling them from the Helm release name and chart name, which is crucial for complex deployments or when integrating with existing systems.
When you deploy a chart with dependencies, Helm doesn’t just copy the subchart files. It actually downloads them into the charts/ directory (or fetches them from a chart repository) and then merges their values with your parent chart’s values. The order of precedence is important: values provided on the command line (--set) override values in values.yaml, which override values in the subchart’s values.yaml.
The true magic of Helm lies in its ability to transform a static set of YAML files into dynamic, environment-aware Kubernetes deployments through the interplay of templating and values.
The next concept you’ll likely encounter is managing complex dependencies and dealing with chart version conflicts.