Kubernetes Helm Charts: Packaging Applications

devops kubernetes helm

Raw Kubernetes YAML gets repetitive. Helm packages applications into reusable charts with templating and configuration management.

What is Helm?

Helm is:

Think apt/yum/npm for Kubernetes.

Installation

# macOS
brew install helm

# Linux
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

Using Existing Charts

Add Repositories

# Add official stable repo
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

Search Charts

helm search repo postgresql
helm search hub postgresql  # Search Artifact Hub

Install a Chart

# Basic install
helm install my-postgres bitnami/postgresql

# With custom values
helm install my-postgres bitnami/postgresql -f values.yaml

# Or inline values
helm install my-postgres bitnami/postgresql --set auth.postgresPassword=secret

Manage Releases

# List releases
helm list

# Check status
helm status my-postgres

# Upgrade
helm upgrade my-postgres bitnami/postgresql --set replicas=3

# Rollback
helm rollback my-postgres 1

# Uninstall
helm uninstall my-postgres

Creating Your Own Chart

Scaffold

helm create my-app

Creates:

my-app/
├── Chart.yaml          # Metadata
├── values.yaml         # Default configuration
├── templates/          # Kubernetes manifests with templating
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   └── _helpers.tpl    # Template helpers
└── charts/             # Dependencies

Chart.yaml

apiVersion: v2
name: my-app
description: My application Helm chart
type: application
version: 0.1.0
appVersion: "1.0.0"

values.yaml

replicaCount: 1

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

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false
  hostname: myapp.example.com

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

Template Example

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "my-app.fullname" . }}
  labels:
    {{- include "my-app.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "my-app.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "my-app.selectorLabels" . | nindent 8 }}
    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 }}

Template Functions

# String functions
{{ .Values.name | upper }}
{{ .Values.name | quote }}
{{ .Values.name | default "default-value" }}

# Conditionals
{{- if .Values.ingress.enabled }}
# Ingress config here
{{- end }}

# Loops
{{- range .Values.extraEnv }}
- name: {{ .name }}
  value: {{ .value }}
{{- end }}

# Include other templates
{{ include "my-app.fullname" . }}

_helpers.tpl

{{/*
Create a default fully qualified app name.
*/}}
{{- define "my-app.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}

Testing Charts

Lint

helm lint my-app

Template Preview

# See generated YAML
helm template my-app ./my-app

# With custom values
helm template my-app ./my-app -f production-values.yaml

Dry Run

helm install my-app ./my-app --dry-run --debug

Chart Dependencies

# Chart.yaml
dependencies:
  - name: postgresql
    version: "11.x.x"
    repository: "https://charts.bitnami.com/bitnami"
helm dependency update

Versioning and Releases

Semantic Versioning

# Chart.yaml
version: 1.2.3     # Chart version
appVersion: "2.0.0" # Application version

Packaging

helm package my-app
# Creates my-app-0.1.0.tgz

Hosting

# Create index
helm repo index . --url https://charts.example.com

# Or use Chart Museum
docker run -p 8080:8080 chartmuseum/chartmuseum

Best Practices

Use values.yaml Wisely

# Good: Sensible defaults
replicaCount: 1

# Good: Override for production
# values-production.yaml
replicaCount: 3

Resource Limits

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

Always set resource limits.

Labels

labels:
  app.kubernetes.io/name: {{ .Chart.Name }}
  app.kubernetes.io/instance: {{ .Release.Name }}
  app.kubernetes.io/version: {{ .Chart.AppVersion }}
  app.kubernetes.io/managed-by: {{ .Release.Service }}

Standard labels for identification.

Notes.txt

# templates/NOTES.txt
Thank you for installing {{ .Chart.Name }}.

To get the application URL:
{{- if .Values.ingress.enabled }}
  http://{{ .Values.ingress.hostname }}
{{- else }}
  kubectl port-forward svc/{{ include "my-app.fullname" . }} 8080:80
{{- end }}

Helpful post-install instructions.

Helm vs Kustomize

AspectHelmKustomize
ApproachTemplatingPatching
Learning curveHigherLower
Package managementYesNo
Release managementYesNo
ComplexityHigherLower

Use Helm for complex apps. Kustomize for simple overlays.

Final Thoughts

Helm standardizes Kubernetes deployment. Instead of copying YAML files, you configure charts. Instead of manual rollbacks, you helm rollback.

Start by using existing charts. Graduate to writing your own when you understand the patterns.


Package once, deploy everywhere.

All posts