Featured Article Kubernetes DevOps

Helm: Kubernetes Package Management and Chart Development Guide

Complete guide to Helm for Kubernetes—from basic charts to advanced patterns, including Helmfile, chart repositories, and testing strategies.

HA
Hari Prasad
October 10, 2024
5 min read ...
Financial Planning Tool

PPF Calculator

Calculate your Public Provident Fund returns with detailed projections and tax benefits. Plan your financial future with precision.

Try Calculator
Free Forever Secure
10K+
Users
4.9★
Rating
Career Tool

Resume Builder

Create professional DevOps resumes with modern templates. Showcase your skills, experience, and certifications effectively.

Build Resume
No Login Export PDF
15+
Templates
5K+
Created
Kubernetes Tool

EKS Pod Cost Calculator

Calculate Kubernetes pod costs on AWS EKS. Optimize resource allocation and reduce your cloud infrastructure expenses.

Calculate Costs
Accurate Real-time
AWS
EKS Support
$$$
Save Money
AWS Cloud Tool

AWS VPC Designer Pro

Design and visualize AWS VPC architectures with ease. Create production-ready network diagrams with subnets, route tables, and security groups in minutes.

Design VPC
Visual Editor Export IaC
Multi-AZ
HA Design
Pro
Features
Subnets Security Routing
Explore More

Discover My DevOps Journey

Explore my portfolio, read insightful blogs, learn from comprehensive courses, and leverage powerful DevOps tools—all in one place.

50+
Projects
100+
Blog Posts
10+
Courses
20+
Tools

Helm is the package manager for Kubernetes, simplifying deployment and management of complex applications. This comprehensive guide covers everything from basic concepts to advanced chart development and best practices.

Why Helm?

Helm solves several Kubernetes deployment challenges:

  • Templating: Dynamic YAML generation with values
  • Versioning: Track and rollback releases
  • Dependency Management: Manage chart dependencies
  • Reusability: Share and reuse configurations
  • Simplified Operations: Install/upgrade/rollback with single commands

Installation

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

# Install on macOS
brew install helm

# Install on Ubuntu/Debian
curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
sudo apt-get install apt-transport-https --yes
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm

# Verify installation
helm version

# Add shell completion
helm completion bash >> ~/.bashrc
source ~/.bashrc

Getting Started

# Add a repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

# Search for charts
helm search repo nginx
helm search hub nginx

# Install a chart
helm install my-nginx bitnami/nginx

# List releases
helm list
helm list --all-namespaces

# Get release info
helm get all my-nginx
helm get values my-nginx
helm get manifest my-nginx

# Upgrade release
helm upgrade my-nginx bitnami/nginx --set replicaCount=3

# Rollback release
helm rollback my-nginx 1

# Uninstall release
helm uninstall my-nginx

Creating Your First Chart

# Create chart
helm create myapp

# Directory structure
myapp/
├── Chart.yaml          # Chart metadata
├── values.yaml         # Default values
├── charts/             # Chart dependencies
├── templates/          # Template files
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── _helpers.tpl    # Template helpers
│   ├── NOTES.txt       # Post-install notes
│   └── tests/          # Test files
└── .helmignore         # Files to ignore

Chart.yaml

# Chart.yaml
apiVersion: v2
name: myapp
description: A Helm chart for my application
type: application
version: 1.0.0
appVersion: "1.0.0"

keywords:
  - web
  - application
  - nodejs

maintainers:
  - name: DevOps Team
    email: devops@example.com
    url: https://example.com

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

values.yaml

# values.yaml
replicaCount: 3

image:
  repository: myregistry.com/myapp
  pullPolicy: IfNotPresent
  tag: "1.0.0"

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

serviceAccount:
  create: true
  annotations: {}
  name: ""

podAnnotations: {}
podSecurityContext:
  runAsNonRoot: true
  runAsUser: 1000
  fsGroup: 2000

securityContext:
  capabilities:
    drop:
    - ALL
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false

service:
  type: ClusterIP
  port: 80
  targetPort: 8080
  annotations: {}

ingress:
  enabled: false
  className: "nginx"
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: myapp.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: myapp-tls
      hosts:
        - myapp.example.com

resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 250m
    memory: 256Mi

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 80
  targetMemoryUtilizationPercentage: 80

nodeSelector: {}
tolerations: []
affinity: {}

# Application-specific values
config:
  logLevel: info
  database:
    host: postgresql
    port: 5432
    name: myapp
  redis:
    host: redis
    port: 6379

postgresql:
  enabled: true
  auth:
    database: myapp
    username: myapp
  primary:
    persistence:
      size: 10Gi

redis:
  enabled: true
  architecture: standalone
  master:
    persistence:
      size: 5Gi

Deployment Template

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "myapp.fullname" . }}
  labels:
    {{- include "myapp.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "myapp.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
        {{- with .Values.podAnnotations }}
        {{- toYaml . | nindent 8 }}
        {{- end }}
      labels:
        {{- include "myapp.selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "myapp.serviceAccountName" . }}
      securityContext:
        {{- toYaml .Values.podSecurityContext | nindent 8 }}
      containers:
      - name: {{ .Chart.Name }}
        securityContext:
          {{- toYaml .Values.securityContext | nindent 12 }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        ports:
        - name: http
          containerPort: {{ .Values.service.targetPort }}
          protocol: TCP
        env:
        - name: LOG_LEVEL
          value: {{ .Values.config.logLevel | quote }}
        - name: DB_HOST
          value: {{ .Values.config.database.host | quote }}
        - name: DB_PORT
          value: {{ .Values.config.database.port | quote }}
        - name: DB_NAME
          value: {{ .Values.config.database.name | quote }}
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: {{ include "myapp.fullname" . }}-db
              key: password
        livenessProbe:
          httpGet:
            path: /health
            port: http
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: http
          initialDelaySeconds: 10
          periodSeconds: 5
        resources:
          {{- toYaml .Values.resources | nindent 12 }}
        volumeMounts:
        - name: config
          mountPath: /app/config
          readOnly: true
        - name: tmp
          mountPath: /tmp
      volumes:
      - name: config
        configMap:
          name: {{ include "myapp.fullname" . }}
      - name: tmp
        emptyDir: {}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}

Helper Templates

# templates/_helpers.tpl
{{/*
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 }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "myapp.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "myapp.labels" -}}
helm.sh/chart: {{ include "myapp.chart" . }}
{{ include "myapp.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "myapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Create the name of the service account to use
*/}}
{{- define "myapp.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "myapp.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

Chart Management

Install and Upgrade

# Install with custom values
helm install myapp ./myapp \
  --namespace production \
  --create-namespace \
  --values values-production.yaml \
  --set image.tag=1.2.0

# Dry run
helm install myapp ./myapp --dry-run --debug

# Upgrade
helm upgrade myapp ./myapp \
  --values values-production.yaml \
  --set replicaCount=5

# Install or upgrade
helm upgrade --install myapp ./myapp \
  --values values-production.yaml

# Wait for resources to be ready
helm upgrade myapp ./myapp --wait --timeout 5m

# Force recreation
helm upgrade myapp ./myapp --force

# Atomic upgrade (rollback on failure)
helm upgrade myapp ./myapp --atomic --timeout 10m

Multiple Values Files

# values-dev.yaml
replicaCount: 1
ingress:
  hosts:
    - host: dev.example.com
config:
  logLevel: debug
postgresql:
  enabled: true
---
# values-staging.yaml
replicaCount: 2
ingress:
  hosts:
    - host: staging.example.com
config:
  logLevel: info
---
# values-production.yaml
replicaCount: 5
ingress:
  enabled: true
  hosts:
    - host: example.com
config:
  logLevel: warn
autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 10
# Install with multiple values files
helm install myapp ./myapp \
  -f values.yaml \
  -f values-production.yaml \
  --set image.tag=$(git rev-parse --short HEAD)

Advanced Patterns

Conditionals

# templates/ingress.yaml
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "myapp.fullname" . }}
  {{- with .Values.ingress.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  {{- if .Values.ingress.className }}
  ingressClassName: {{ .Values.ingress.className }}
  {{- end }}
  {{- if .Values.ingress.tls }}
  tls:
    {{- range .Values.ingress.tls }}
    - hosts:
        {{- range .hosts }}
        - {{ . | quote }}
        {{- end }}
      secretName: {{ .secretName }}
    {{- end }}
  {{- end }}
  rules:
    {{- range .Values.ingress.hosts }}
    - host: {{ .host | quote }}
      http:
        paths:
          {{- range .paths }}
          - path: {{ .path }}
            pathType: {{ .pathType }}
            backend:
              service:
                name: {{ include "myapp.fullname" $ }}
                port:
                  number: {{ $.Values.service.port }}
          {{- end }}
    {{- end }}
{{- end }}

Loops

# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "myapp.fullname" . }}
data:
  {{- range $key, $value := .Values.config }}
  {{ $key }}: {{ $value | quote }}
  {{- end }}

Named Templates with Arguments

# templates/_helpers.tpl
{{- define "myapp.envVars" -}}
{{- range $key, $value := . }}
- name: {{ $key }}
  value: {{ $value | quote }}
{{- end }}
{{- end }}

# Usage in deployment
env:
{{- include "myapp.envVars" .Values.env | nindent 2 }}

Dependencies

# Chart.yaml
dependencies:
  - name: postgresql
    version: "~12.0.0"
    repository: https://charts.bitnami.com/bitnami
    condition: postgresql.enabled
  - name: redis
    version: "^17.0.0"
    repository: "@bitnami"
    condition: redis.enabled
    alias: cache
# Update dependencies
helm dependency update

# List dependencies
helm dependency list

# Build dependencies
helm dependency build

Hooks

# templates/job-migration.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "myapp.fullname" . }}-migration
  annotations:
    "helm.sh/hook": pre-upgrade,pre-install
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
  template:
    metadata:
      name: {{ include "myapp.fullname" . }}-migration
    spec:
      restartPolicy: Never
      containers:
      - name: migration
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        command: ["npm", "run", "migrate"]

Hook types:

  • pre-install: Before resources are installed
  • post-install: After all resources are installed
  • pre-delete: Before any resources are deleted
  • post-delete: After all resources are deleted
  • pre-upgrade: Before resources are upgraded
  • post-upgrade: After all resources are upgraded
  • pre-rollback: Before resources are rolled back
  • post-rollback: After resources are rolled back

Testing

# 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 tests
helm test myapp

# Run tests with logs
helm test myapp --logs

Helmfile

Manage multiple Helm releases:

# helmfile.yaml
repositories:
  - name: bitnami
    url: https://charts.bitnami.com/bitnami
  - name: prometheus-community
    url: https://prometheus-community.github.io/helm-charts

helmDefaults:
  wait: true
  timeout: 600
  recreatePods: false
  force: false

releases:
  - name: postgresql
    namespace: databases
    chart: bitnami/postgresql
    version: ~12.0.0
    values:
      - values/postgresql.yaml
    secrets:
      - secrets/postgresql-secrets.yaml

  - name: myapp
    namespace: production
    chart: ./charts/myapp
    values:
      - values/myapp-production.yaml
    set:
      - name: image.tag
        value: {{ requiredEnv "IMAGE_TAG" }}
    needs:
      - databases/postgresql

  - name: prometheus
    namespace: monitoring
    chart: prometheus-community/kube-prometheus-stack
    version: ~45.0.0
    values:
      - values/prometheus.yaml
# Install helmfile
brew install helmfile

# Sync all releases
helmfile sync

# Apply specific release
helmfile -l name=myapp sync

# Diff changes
helmfile diff

# Destroy all releases
helmfile destroy

Chart Repository

Create Chart Repository

# Package chart
helm package myapp

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

# Upload to GitHub Pages
git add .
git commit -m "Add chart"
git push origin gh-pages

Host on GitHub Pages

# .github/workflows/release.yml
name: Release Charts

on:
  push:
    branches:
      - main

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Configure Git
        run: |
          git config user.name "$GITHUB_ACTOR"
          git config user.email "$GITHUB_ACTOR@users.noreply.github.com"

      - name: Install Helm
        uses: azure/setup-helm@v3

      - name: Run chart-releaser
        uses: helm/chart-releaser-action@v1.5.0
        env:
          CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

Best Practices

Use specific chart versions
Document values in values.yaml
Use _helpers.tpl for reusable templates
Implement health checks
Add resource limits
Use hooks for migrations
Test charts before deployment
Version charts semantically
Use .helmignore
Add NOTES.txt for post-install guidance

Troubleshooting

# Debug template rendering
helm template myapp ./myapp

# Debug with values
helm template myapp ./myapp -f values-production.yaml

# Show computed values
helm get values myapp

# Show all values (including defaults)
helm get values myapp --all

# View manifest
helm get manifest myapp

# View history
helm history myapp

# Detailed status
helm status myapp

# Lint chart
helm lint ./myapp

# Validate against cluster
helm lint ./myapp --strict

Conclusion

Helm streamlines Kubernetes application management through templating, versioning, and dependency management. By following these best practices and patterns, you’ll create maintainable, reusable charts that simplify complex deployments across multiple environments.

Resources


How do you use Helm in your workflows? Share your tips in the comments!

HA
Author

Hari Prasad

Seasoned DevOps Lead with 11+ years of expertise in cloud infrastructure, CI/CD automation, and infrastructure as code. Proven track record in designing scalable, secure systems on AWS using Terraform, Kubernetes, Jenkins, and Ansible. Strong leadership in mentoring teams and implementing cost-effective cloud solutions.

Continue Reading

DevOps Tools & Calculators Free Tools

Power up your DevOps workflow with these handy tools

Enjoyed this article?

Explore more DevOps insights, tutorials, and best practices

View All Articles