Helm Charts

The atomic packaging unit of Helm. A chart is a directory of files that describes a set of Kubernetes resources using Go templates, default values, and metadata. Charts turn complex multi-resource applications into reusable, versioned, and configurable packages. Synthesized from Helm Zero to Hero by @AbhishekVeeramalla.

Chart Directory Structure

A chart is a filesystem tree with a mandatory top-level Chart.yaml and a templates/ directory:

myapp/
├── Chart.yaml          # Chart metadata and dependencies
├── values.yaml         # Default configuration values
├── values.schema.json  # Optional JSON schema for values validation
├── charts/             # Sub-charts (dependencies)
├── templates/          # Kubernetes manifest templates
│   ├── _helpers.tpl    # Named template helpers
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── hpa.yaml
│   └── NOTES.txt       # Post-install notes shown to user
├── templates/tests/    # Test manifests (run by helm test)
└── README.md           # Human-readable documentation

Every file in templates/ is rendered through Go’s text/template engine. Files starting with an underscore (_) are not rendered as standalone manifests — they are intended for inclusion via {{ include }} or {{ template }}.

Chart.yaml — Metadata and Dependencies

apiVersion: v2
name: myapp
description: A Helm chart for the MyApp microservice
type: application        # or "library" for reusable helper charts
version: 1.2.0           # chart version (follows SemVer)
appVersion: "2.5.1"      # the application version this chart deploys
keywords:
  - web
  - microservice
home: https://github.com/org/myapp
sources:
  - https://github.com/org/myapp
maintainers:
  - name: DevOps Team
    email: [email protected]
dependencies:
  - name: postgresql
    version: 12.0.0
    repository: https://charts.bitnami.com/bitnami
    condition: postgresql.enabled
    tags:
      - database
  • apiVersion: v2 is required for Helm 3 charts (v1 is Helm 2 legacy)
  • version is the chart’s own SemVer — incremented when templates or defaults change
  • appVersion is the application being deployed — useful for correlating chart releases with application releases
  • dependencies declares sub-charts that Helm fetches and installs alongside the parent

values.yaml — Default Configuration

values.yaml is the contract between chart author and chart consumer. It declares every tunable parameter with sensible defaults:

replicaCount: 2
 
image:
  repository: nginx
  pullPolicy: IfNotPresent
  tag: "1.27"
 
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
 
service:
  type: ClusterIP
  port: 80
 
ingress:
  enabled: false
  className: "nginx"
  annotations: {}
  hosts:
    - host: chart-example.local
      paths:
        - path: /
          pathType: Prefix
  tls: []
 
resources:
  limits:
    cpu: 200m
    memory: 256Mi
  requests:
    cpu: 100m
    memory: 128Mi
 
autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 10
  targetCPUUtilizationPercentage: 80

Best practices for values design:

  • Flatten logically: group related settings under a key (image.repository, not imagerepository)
  • Use booleans for features: ingress.enabled: false lets consumers opt in cleanly
  • Keep secrets out: never put credentials in default values; require consumers to inject via external Secret mechanisms
  • Document in README: every top-level key should have a one-line explanation

Templating with Go Templates

Helm manifests are Go templates. The template engine injects values and provides helper functions.

Built-in Objects

ObjectDescription
.ValuesContents of values.yaml plus CLI overrides
.ReleaseInformation about the release: .Name, .Namespace, .Revision, .IsUpgrade, .IsInstall
.ChartContents of Chart.yaml: .Name, .Version, .AppVersion
.CapabilitiesCluster capabilities: .KubeVersion, .APIVersions
.TemplateCurrent template being executed: .Name, .BasePath

Common Template Patterns

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "myapp.fullname" . }}
  labels:
    {{- include "myapp.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "myapp.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "myapp.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: {{ .Values.service.port }}
              protocol: TCP
          resources:
            {{- toYaml .Values.resources | nindent 12 }}

Key syntax notes:

  • {{ ... }} outputs the result
  • {{- ... }} strips leading whitespace (critical for YAML validity)
  • | nindent 4 indents a multi-line string by 4 spaces
  • | default .Chart.AppVersion provides a fallback if no tag is specified
  • toYaml serializes an object as YAML (commonly used for resources, nodeSelector, tolerations)

Named Templates in _helpers.tpl

The _helpers.tpl file defines reusable snippets:

{{/*
Expand the name of the chart.
*/}}
{{- define "myapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
 
{{/*
Create a default fully qualified app name.
*/}}
{{- define "myapp.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 }}
{{- end }}

Using named templates keeps manifests DRY and ensures consistent naming across Services, Deployments, and Ingresses.

Chart Dependencies (Sub-charts)

Complex applications often need databases, caches, or message brokers. Helm models these as dependencies declared in Chart.yaml:

dependencies:
  - name: redis
    version: 17.x.x
    repository: https://charts.bitnami.com/bitnami
    condition: redis.enabled
  - name: postgresql
    version: 12.x.x
    repository: https://charts.bitnami.com/bitnami
    alias: db

Helm downloads dependencies into the charts/ directory during helm dependency update. The condition field allows consumers to toggle sub-charts via values.yaml:

redis:
  enabled: true
  architecture: standalone
db:
  enabled: false

The alias field renames the dependency’s values namespace — useful when you need two instances of the same chart (e.g., two databases).

Creating a Chart from Scratch

# Scaffold a new chart
helm create myapp
 
# Customize templates, values, and metadata
# Then validate
helm lint ./myapp
 
# Preview rendered output
helm template myapp ./myapp
 
# Install for real
helm install myapp ./myapp

The helm create scaffold includes a working NGINX example. Replace the templates and values to match your application.

Packaging and Distribution

Charts are distributed as .tgz archives with a SHA256 checksum:

# Package the chart
helm package myapp        # produces myapp-1.2.0.tgz
 
# Generate repository index
helm repo index . --url https://example.com/charts
 
# The resulting index.yaml looks like:
# apiVersion: v1
# entries:
#   myapp:
#     - apiVersion: v2
#       appVersion: 2.5.1
#       version: 1.2.0
#       urls:
#         - https://example.com/charts/myapp-1.2.0.tgz
#       digest: sha256:abc123...

Host index.yaml and .tgz files on any static web server (GitHub Pages, S3, GCS, Artifactory, Nexus). Consumers add the repository with helm repo add and install charts by name.

Chart Testing

Helm supports verification tests that run as short-lived Jobs after installation:

# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
  name: "{{ include "myapp.fullname" . }}-test-connection"
  annotations:
    "helm.sh/hook": test
spec:
  containers:
    - name: wget
      image: busybox
      command: ['wget']
      args: ['{{ include "myapp.fullname" . }}:{{ .Values.service.port }}']
  restartPolicy: Never

Run with helm test myapp. These tests validate that the deployed application is actually reachable and healthy.


Tags: helm charts kubernetes go-templates templating devops packaging cncf