Understanding Helm Charts - From Confusion to Clarity

What is a Helm Chart? (Simple Answer)
A Helm chart is a ZIP file containing:
Kubernetes YAML templates (Deployment, Service, etc.)
A
values.yamlfile with default configurationA 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:
Helm downloads the chart
Helm reads the chart's
values.yaml(defaults)Helm reads YOUR
my-postgres-values.yaml(overrides)Helm merges them (your values override defaults)
Helm fills the templates with the merged values
Helm applies the rendered YAML to Kubernetes
The Three Ways to Provide Values
Method 1: Values File (Recommended)
# 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
Step 2: Open and Search
# 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
Chart = Templates + Default Values
You only configure values, NOT templates
Your values OVERRIDE defaults (you don't need to copy everything)
Match the EXACT structure from default values.yaml
Use
helm show valuesto see what you can configureUse
helm templateor--dry-runto verify before installingValues 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

