Skip to main content

Command Palette

Search for a command to run...

Understanding Helm Charts - From Confusion to Clarity

Updated
9 min read
Understanding Helm Charts - From Confusion to Clarity
H
DevOps Engineer

What is a Helm Chart? (Simple Answer)

A Helm chart is a ZIP file containing:

  1. Kubernetes YAML templates (Deployment, Service, etc.)

  2. A values.yaml file with default configuration

  3. A way to customize those templates

Think of it like:

  • Template = A form with blank fields

  • values.yaml = The default values to fill in those blanks

  • Your custom values = Overriding those defaults


The Confusion: Templates vs Values

What Confuses People:

❓ "There's a Deployment template in the chart..."
❓ "There's a values.yaml with settings..."
❓ "How do these connect?"
❓ "What can I actually change?"
❓ "Where do I put MY configuration?"

The Answer (Visual):

HELM CHART (what the chart maintainers created)
│
├── templates/
│   ├── deployment.yaml          ← Template with {{ .Values.* }} placeholders
│   ├── service.yaml             ← Template with {{ .Values.* }} placeholders
│   └── configmap.yaml           ← Template with {{ .Values.* }} placeholders
│
└── values.yaml                  ← DEFAULT values that fill those placeholders
    image:
      repository: postgres
      tag: "15"
    persistence:
      enabled: true
      size: 8Gi

YOUR CUSTOM VALUES (what you provide)
│
└── my-values.yaml               ← YOUR values that OVERRIDE defaults
    image:
      tag: "16"                  ← Override: Use version 16 instead
    persistence:
      size: 20Gi                 ← Override: Use 20Gi instead of 8Gi

RESULT (what actually gets deployed)
│
└── Kubernetes YAML              ← Template filled with YOUR values
    apiVersion: apps/v1
    kind: Deployment
    spec:
      template:
        spec:
          containers:
          - image: postgres:16    ← From your my-values.yaml
            volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: postgres-pvc
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    spec:
      resources:
        requests:
          storage: 20Gi           ← From your my-values.yaml

How to Actually Use a Helm Chart (Step by Step)

Step 1: Find the Chart

# Search for a chart
helm search hub postgresql

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

Step 2: Look at What You Can Configure

# See ALL available options
helm show values bitnami/postgresql

# This outputs the ENTIRE values.yaml - save it!
helm show values bitnami/postgresql > default-values.yaml

What you see (example):

## @section Global parameters
global:
  imageRegistry: ""
  storageClass: ""

## @section Common parameters
architecture: standalone

## @section PostgreSQL Authentication parameters
auth:
  enablePostgresUser: true
  postgresPassword: ""
  username: ""
  password: ""
  database: ""

## @section PostgreSQL Primary parameters
primary:
  persistence:
    enabled: true
    storageClass: ""
    size: 8Gi

  resources:
    limits: {}
    requests:
      memory: 256Mi
      cpu: 250m

Step 3: Understand the Structure

The values.yaml has a HIERARCHY:

# Top level
auth:                     Top-level key
  username: "myapp"       Nested under 'auth'
  password: "secret"      Nested under 'auth'
  database: "mydb"        Nested under 'auth'

primary:                  Different top-level key
  persistence:            Nested under 'primary'
    enabled: true         Nested under 'persistence'
    size: 20Gi           Nested under 'persistence'
  resources:              Different nested key under 'primary'
    limits:               Nested under 'resources'
      cpu: 2000m          Nested under 'limits'

This maps to template placeholders:

# In the chart's templates/deployment.yaml (you don't edit this):
spec:
  containers:
  - name: postgresql
    image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
    env:
    - name: POSTGRES_USER
      value: {{ .Values.auth.username }}
    - name: POSTGRES_PASSWORD
      value: {{ .Values.auth.password }}
    - name: POSTGRES_DB
      value: {{ .Values.auth.database }}

When you provide:

# your-values.yaml
auth:
  username: "myapp"
  password: "secret123"
  database: "myappdb"

Helm renders it as:

spec:
  containers:
  - name: postgresql
    image: postgres:15
    env:
    - name: POSTGRES_USER
      value: myapp           YOUR value inserted here
    - name: POSTGRES_PASSWORD
      value: secret123       YOUR value inserted here
    - name: POSTGRES_DB
      value: myappdb         YOUR value inserted here

Step 4: Create Your Custom Values File

DON'T copy the entire default values.yaml!

DO only specify what you want to CHANGE:

# my-postgres-values.yaml

# Only include what you're changing!
auth:
  username: myapp
  password: mySecurePassword123
  database: myappdb

primary:
  persistence:
    size: 20Gi

  resources:
    limits:
      cpu: 2000m
      memory: 4Gi
    requests:
      cpu: 500m
      memory: 1Gi

Everything else uses the chart's defaults!

Step 5: Install the Chart

# Install with your custom values
helm install my-postgres bitnami/postgresql -f my-postgres-values.yaml

# Or install to specific namespace
helm install my-postgres bitnami/postgresql \
  --namespace database \
  --create-namespace \
  -f my-postgres-values.yaml

What happens:

  1. Helm downloads the chart

  2. Helm reads the chart's values.yaml (defaults)

  3. Helm reads YOUR my-postgres-values.yaml (overrides)

  4. Helm merges them (your values override defaults)

  5. Helm fills the templates with the merged values

  6. Helm applies the rendered YAML to Kubernetes


The Three Ways to Provide Values

# Create my-values.yaml
cat > my-values.yaml <<EOF
auth:
  username: myapp
  database: myappdb
EOF

# Install with file
helm install postgres bitnami/postgresql -f my-values.yaml

Method 2: Command Line (Quick Testing)

# Set individual values via --set
helm install postgres bitnami/postgresql \
  --set auth.username=myapp \
  --set auth.database=myappdb \
  --set primary.persistence.size=20Gi

Method 3: Multiple Values Files (Advanced)

# Combine multiple files
helm install postgres bitnami/postgresql \
  -f base-values.yaml \
  -f production-overrides.yaml

# Later values override earlier ones

Common Confusion Points - SOLVED

Confusion 1: "What can I actually configure?"

Answer: Look at the default values.yaml

helm show values bitnami/postgresql > values.yaml
less values.yaml

# Search for what you need
grep -i "persistence" values.yaml
grep -i "resources" values.yaml
grep -i "password" values.yaml

If it's in values.yaml, you can configure it!

Confusion 2: "How do I know the correct structure?"

Answer: Copy the structure EXACTLY from values.yaml

# ❌ WRONG - flat structure
persistence.size: 20Gi

# ✅ CORRECT - nested structure (same as in values.yaml)
primary:
  persistence:
    size: 20Gi

Rule: Match the indentation/nesting from the default values.yaml

Confusion 3: "Do I need to specify everything?"

NO! Only specify what you want to change.

# ❌ Don't do this (500 lines)
# Copy entire values.yaml and modify a few lines

# ✅ Do this (10 lines)
# Only what you're changing
auth:
  username: myapp
  database: myappdb

Confusion 4: "What if I want to see the final YAML?"

# Dry-run: See what will be created WITHOUT installing
helm install postgres bitnami/postgresql \
  -f my-values.yaml \
  --dry-run \
  --debug

# This shows you the FINAL rendered Kubernetes YAML

Confusion 5: "How do I know if my values are valid?"

# Template: Render locally without installing
helm template postgres bitnami/postgresql -f my-values.yaml

# If it works, your values are valid
# If it fails, you'll see the error

Real Example: From Confusion to Clarity

Scenario: Deploy PostgreSQL

Step 1: Get Default Values

helm repo add bitnami https://charts.bitnami.com/bitnami
helm show values bitnami/postgresql > postgres-defaults.yaml
# Open in your editor
code postgres-defaults.yaml

# Search for key sections (Ctrl+F):
# - "auth"          ← Authentication
# - "persistence"   ← Storage
# - "resources"     ← CPU/Memory
# - "initdb"        ← Initial scripts

Step 3: Find What You Need

In postgres-defaults.yaml you find:

## Authentication parameters
auth:
  ## @param auth.enablePostgresUser Assign a password to the "postgres" admin user
  enablePostgresUser: true
  ## @param auth.postgresPassword Password for the "postgres" admin user
  postgresPassword: ""
  ## @param auth.username Name for a custom user to create
  username: ""
  ## @param auth.password Password for the custom user to create
  password: ""
  ## @param auth.database Name for a custom database to create
  database: ""

And:

primary:
  ## PostgreSQL Primary persistence configuration
  persistence:
    ## @param primary.persistence.enabled Enable PostgreSQL Primary data persistence
    enabled: true
    ## @param primary.persistence.storageClass PVC Storage Class
    storageClass: ""
    ## @param primary.persistence.size PVC Storage Request
    size: 8Gi

Step 4: Create YOUR Values (Only What You Need)

# my-postgres-values.yaml

# Authentication (copied structure from defaults)
auth:
  username: myapp
  password: MySecurePass123
  database: myappdb

# Storage (copied structure from defaults)
primary:
  persistence:
    enabled: true
    storageClass: local-path  # K3s storage class
    size: 20Gi                # Changed from default 8Gi

# Resources (copied structure from defaults)
  resources:
    limits:
      cpu: 2000m
      memory: 4Gi
    requests:
      cpu: 500m
      memory: 1Gi

Step 5: Verify Before Installing

# See what will be created
helm template test-postgres bitnami/postgresql \
  -f my-postgres-values.yaml \
  --namespace database

# Look through the output - verify:
# - POSTGRES_USER = myapp ✓
# - POSTGRES_DB = myappdb ✓
# - PVC size = 20Gi ✓
# - CPU limits = 2000m ✓

Step 6: Install

helm install postgres bitnami/postgresql \
  -f my-postgres-values.yaml \
  --namespace database \
  --create-namespace

The Mental Model

┌─────────────────────────────────────────┐
│         HELM CHART (Package)            │
│  ┌────────────────────────────────┐    │
│  │  templates/                     │    │
│  │    deployment.yaml              │    │
│  │    service.yaml                 │    │
│  │    configmap.yaml               │    │
│  │                                 │    │
│  │  These have placeholders:       │    │
│  │    {{ .Values.auth.username }}  │    │
│  │    {{ .Values.persistence.size }}│   │
│  └────────────────────────────────┘    │
│                                         │
│  ┌────────────────────────────────┐    │
│  │  values.yaml (DEFAULTS)         │    │
│  │    auth:                        │    │
│  │      username: ""               │    │
│  │    persistence:                 │    │
│  │      size: 8Gi                  │    │
│  └────────────────────────────────┘    │
└─────────────────────────────────────────┘
                    │
                    │ You provide
                    ↓
┌─────────────────────────────────────────┐
│   YOUR VALUES (my-values.yaml)          │
│     auth:                               │
│       username: myapp  ← Override       │
│     persistence:                        │
│       size: 20Gi       ← Override       │
└─────────────────────────────────────────┘
                    │
                    │ Helm merges
                    ↓
┌─────────────────────────────────────────┐
│       FINAL RENDERED YAML               │
│   (Actually deployed to Kubernetes)     │
│                                         │
│   Deployment with:                      │
│     - username: myapp                   │
│     - PVC size: 20Gi                    │
└─────────────────────────────────────────┘

Quick Reference Commands

# Search for charts
helm search hub <chart-name>

# Add repository
helm repo add <name> <url>
helm repo update

# View chart info
helm show chart <repo>/<chart>

# View README
helm show readme <repo>/<chart>

# View default values (IMPORTANT!)
helm show values <repo>/<chart>
helm show values <repo>/<chart> > values.yaml

# Test your values without installing
helm template <name> <repo>/<chart> -f my-values.yaml
helm install <name> <repo>/<chart> -f my-values.yaml --dry-run --debug

# Install chart
helm install <name> <repo>/<chart> -f my-values.yaml
helm install <name> <repo>/<chart> -f my-values.yaml -n <namespace> --create-namespace

# Upgrade chart
helm upgrade <name> <repo>/<chart> -f my-values.yaml

# Check what's installed
helm list
helm list -n <namespace>

# Get values of installed release
helm get values <name>
helm get values <name> -n <namespace>

# Uninstall
helm uninstall <name>
helm uninstall <name> -n <namespace>

Key Takeaways

  1. Chart = Templates + Default Values

  2. You only configure values, NOT templates

  3. Your values OVERRIDE defaults (you don't need to copy everything)

  4. Match the EXACT structure from default values.yaml

  5. Use helm show values to see what you can configure

  6. Use helm template or --dry-run to verify before installing

  7. Values hierarchy must match (indentation matters!)


Practice Exercise

Try this right now:

# 1. Get the default values
helm repo add bitnami https://charts.bitnami.com/bitnami
helm show values bitnami/nginx > nginx-defaults.yaml

# 2. Open and find these sections:
# - replicaCount
# - service.type
# - ingress.enabled

# 3. Create your own values file:
cat > my-nginx-values.yaml <<EOF
replicaCount: 3
service:
  type: NodePort
EOF

# 4. See what it will create:
helm template test bitnami/nginx -f my-nginx-values.yaml

# 5. If it looks good, install:
helm install mynginx bitnami/nginx -f my-nginx-values.yaml