Skip to content
Back to Blog
DevOps

Helm Charts: Packaging Kubernetes Applications

Create, publish, and manage Helm charts for Kubernetes: templates, values files, hooks, chart dependencies, and private repositories.

Sep 2025
14 min read

Introduction

Helm is the package manager for Kubernetes. Just as apt manages packages on Ubuntu or npm manages Node.js dependencies, Helm manages Kubernetes application deployments. Without Helm, deploying even a simple application requires maintaining dozens of YAML files manually. With Helm, you create charts (packages) that can be versioned, shared, and deployed with a single command. This guide teaches you to use and create Helm charts professionally.

Installing Helm

BASH
# Install Helm (Linux)
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# Verify
helm version
# version.BuildInfo{Version:"v3.13.3", ...}

# Add common repositories
helm repo add stable https://charts.helm.sh/stable
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

Using Existing Helm Charts

BASH
# Search for charts
helm search repo nginx
helm search hub redis   # Search Artifact Hub (public registry)

# Install nginx ingress controller
helm install nginx-ingress ingress-nginx/ingress-nginx   --namespace ingress-nginx   --create-namespace   --set controller.replicaCount=2   --set controller.service.type=LoadBalancer

# Install with values file
helm install my-redis bitnami/redis   --namespace databases   --create-namespace   -f redis-values.yaml

# Check status
helm list -A                  # List all releases
helm status my-redis -n databases
helm get values my-redis -n databases   # See current values

# Upgrade a release
helm upgrade my-redis bitnami/redis   --namespace databases   --reuse-values   --set auth.password=newpassword

# Rollback to previous version
helm rollback my-redis 1 -n databases

# Uninstall
helm uninstall my-redis -n databases

Creating Your First Helm Chart

BASH
# Scaffold a new chart
helm create myapp

This creates:

TEXT
myapp/
├── Chart.yaml          # Chart metadata
├── values.yaml         # Default values
├── charts/             # Chart dependencies
└── templates/          # Kubernetes manifest templates
    ├── deployment.yaml
    ├── service.yaml
    ├── ingress.yaml
    ├── hpa.yaml
    ├── serviceaccount.yaml
    ├── _helpers.tpl    # Template helper functions
    └── NOTES.txt       # Post-install instructions

Chart.yaml

YAML
# Chart.yaml
apiVersion: v2
name: myapp
description: My production web application
type: application
version: 1.2.0           # Chart version (bump when chart changes)
appVersion: "3.5.1"      # Application version being packaged

dependencies:
  - name: redis
    version: "~18.0"
    repository: "https://charts.bitnami.com/bitnami"
    condition: redis.enabled

Writing Templates

YAML
# 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 }}"
          ports:
            - containerPort: {{ .Values.service.port }}
          env:
            {{- range $key, $val := .Values.env }}
            - name: {{ $key }}
              value: {{ $val | quote }}
            {{- end }}
          {{- if .Values.resources }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          {{- end }}
          {{- if .Values.configMap.enabled }}
          envFrom:
            - configMapRef:
                name: {{ include "myapp.fullname" . }}-config
          {{- end }}

values.yaml

YAML
# values.yaml
replicaCount: 2

image:
  repository: company/myapp
  pullPolicy: IfNotPresent
  tag: ""   # Defaults to Chart.appVersion

service:
  type: ClusterIP
  port: 8080

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: app.company.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: app-tls
      hosts:
        - app.company.com

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

autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 80

env:
  LOG_LEVEL: "info"
  DATABASE_URL: "postgresql://app:pass@postgres:5432/appdb"

configMap:
  enabled: false

redis:
  enabled: false
  auth:
    password: "changeme"

Template Helper Functions (_helpers.tpl)

TEXT
{{/* templates/_helpers.tpl */}}

{{/* Expand chart name */}}
{{- define "myapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/* Create full name */}}
{{- define "myapp.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 }}

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

Environment-Specific Values

BASH
# values-production.yaml
replicaCount: 10
resources:
  requests:
    cpu: 500m
    memory: 512Mi
  limits:
    cpu: 2000m
    memory: 2Gi
autoscaling:
  enabled: true
  minReplicas: 5
  maxReplicas: 50

# values-staging.yaml
replicaCount: 2
resources:
  requests:
    cpu: 100m
    memory: 128Mi
BASH
# Deploy to staging
helm upgrade --install myapp ./myapp   -f values.yaml   -f values-staging.yaml   --namespace staging   --create-namespace

# Deploy to production
helm upgrade --install myapp ./myapp   -f values.yaml   -f values-production.yaml   --namespace production   --set image.tag=v3.5.2

Debugging and Testing

BASH
# Render templates without deploying
helm template myapp ./myapp -f values.yaml

# Validate rendered templates
helm template myapp ./myapp | kubectl apply --dry-run=client -f -

# Lint chart
helm lint ./myapp

# Debug: show computed values
helm install myapp ./myapp --debug --dry-run

# After deployment: view rendered templates that were applied
helm get manifest myapp -n production

Packaging and Publishing

BASH
# Package chart
helm package ./myapp
# Creates: myapp-1.2.0.tgz

# Create chart repository index
helm repo index . --url https://charts.company.com

# Upload to GitHub Pages (simple approach)
git add myapp-1.2.0.tgz index.yaml
git commit -m "Release myapp v1.2.0"
git push

# Or push to OCI registry (modern approach)
helm push myapp-1.2.0.tgz oci://registry.company.com/helm-charts

Helm charts transform ad-hoc Kubernetes deployments into repeatable, version-controlled releases. Start by using community charts for databases and infrastructure, then create your own charts for application deployments.