KubernetesAdvanced
Helm Charts for Beginners — Deploying with Charts on k3s and Kubernetes
ByDomain India Team·DomainIndia Engineering
6 min read24 Apr 20263 views
# Helm Charts for Beginners — Deploying with Charts on k3s and Kubernetes
## Why Helm
Without Helm, a typical app needs ~8-15 YAML files (Deployment, Service, Ingress, ConfigMap, Secret, PVC, HPA, ServiceAccount, RBAC...). Values duplicated across files. Updating 5 apps = editing 75 files.
Helm:
- **Templated YAML** — DRY
- **Versioned releases** — deploy v1.2, rollback to v1.1 instantly
- **Dependencies** — "Chart X needs Redis" handled transparently
- **Repositories** — 5,000+ pre-built charts on Artifact Hub
## Install Helm
```bash
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version
# version.BuildInfo{Version:"v3.14.0"...}
```
Works on any machine that can reach your k3s/K8s cluster.
## Pattern 1 — Install existing charts
Add a repository:
```bash
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
```
Search:
```bash
helm search repo redis
# NAME CHART VERSION APP VERSION DESCRIPTION
# bitnami/redis 19.5.2 7.2.4 Redis...
```
Install:
```bash
helm install mycache bitnami/redis
--namespace cache
--create-namespace
--set auth.password=SuperSecret123
```
Check:
```bash
helm list --all-namespaces
kubectl get pods -n cache
```
Uninstall:
```bash
helm uninstall mycache -n cache
```
That's it — Redis running in K8s with 5 commands.
## Pattern 2 — Customise via values.yaml
Charts accept configuration. Instead of `--set` flags, use a values file:
```bash
helm show values bitnami/redis > my-values.yaml
```
Edit `my-values.yaml`:
```yaml
architecture: standalone # or "replication" for HA
auth:
password: SuperSecret123
master:
persistence:
size: 10Gi
resources:
limits:
memory: 512Mi
cpu: 500m
```
Install with values:
```bash
helm install mycache bitnami/redis -f my-values.yaml -n cache --create-namespace
```
Keep `my-values.yaml` in git. Reproducible deployments.
## Pattern 3 — Upgrade in place
```bash
# Change something in my-values.yaml
helm upgrade mycache bitnami/redis -f my-values.yaml -n cache
```
Helm computes the diff, applies only what changed, keeps revision history.
Rollback to previous version:
```bash
helm history mycache -n cache
# REVISION UPDATED STATUS CHART
# 1 ... deployed redis-19.5.2
# 2 ... deployed redis-19.5.3
# 3 ... deployed redis-19.5.3 (current)
helm rollback mycache 2 -n cache
```
## Pattern 4 — Write your own chart
```bash
helm create myapp
```
Creates:
```
myapp/
├── Chart.yaml # chart metadata
├── values.yaml # default config
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── serviceaccount.yaml
│ ├── _helpers.tpl # reusable template functions
│ └── NOTES.txt # shown after install
└── charts/ # sub-charts (dependencies)
```
`Chart.yaml`:
```yaml
apiVersion: v2
name: myapp
description: My Node.js API
type: application
version: 0.1.0 # chart version
appVersion: "1.0.0" # app version
```
`values.yaml`:
```yaml
replicaCount: 2
image:
repository: ghcr.io/yourorg/myapp
tag: "1.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: traefik
host: myapp.yourcompany.com
tls: true
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
env:
DATABASE_URL: "postgres://..."
JWT_SECRET: "" # set via --set or separate secrets
```
`templates/deployment.yaml`:
```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 }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: 3000
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 10 }}
```
Template language is Go templates with Sprig functions. Common patterns:
```yaml
# Conditional
{{- if .Values.ingress.enabled }}
...
{{- end }}
# Iterate
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value }}
{{- end }}
# Include helper
{{- include "myapp.fullname" . }}
# Default values
image: {{ .Values.image.repository | default "busybox" }}
```
## Test your chart
```bash
# Dry-run — render templates without applying
helm install --dry-run myapp ./myapp
# Diff (requires helm-diff plugin)
helm plugin install https://github.com/databus23/helm-diff
helm diff upgrade myapp ./myapp
# Lint
helm lint ./myapp
```
## Pattern 5 — Secrets management
Never commit secrets in values.yaml.
**Option A — External secrets:**
Use External Secrets Operator with AWS Secrets Manager / HashiCorp Vault. Your chart references the secret name; actual values live outside git.
**Option B — Sealed Secrets:**
```bash
kubeseal < secret.yaml > sealed-secret.yaml
# sealed-secret.yaml is safe to commit — only cluster can decrypt
```
**Option C — Manually via `--set`:**
```bash
helm install myapp ./myapp --set env.JWT_SECRET=$JWT_SECRET
```
OK for CI/CD where secret comes from GitHub Actions env.
## Pattern 6 — Dependencies
`Chart.yaml`:
```yaml
dependencies:
- name: postgresql
version: "15.5.0"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
- name: redis
version: "19.5.2"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
```
Download deps:
```bash
helm dependency update
```
Install with deps:
```bash
helm install myapp ./myapp
--set postgresql.enabled=true
--set postgresql.auth.postgresPassword=secret
--set redis.enabled=true
```
One chart, whole stack (app + DB + cache).
## Pattern 7 — Publish your chart
```bash
helm package ./myapp
# Creates myapp-0.1.0.tgz
# Host on GitHub Pages
helm repo index ./charts-repo
# or use ChartMuseum, Harbor, Artifact Hub
```
Others can:
```bash
helm repo add yourorg https://yourorg.github.io/charts
helm install myapp yourorg/myapp
```
## Pattern 8 — GitOps with Helm
Argo CD or Flux continuously sync your cluster with Helm charts stored in git.
```yaml
# Argo CD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
spec:
source:
repoURL: https://github.com/yourorg/myapp
path: chart
targetRevision: main
helm:
valueFiles: [values-prod.yaml]
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
```
Push to git → Argo CD detects → applies chart. Always-in-sync cluster.
## Common pitfalls
## FAQ
TL;DR
Helm is the npm/apt of Kubernetes. Instead of writing 20 YAML files for a Postgres deployment, you install one Helm chart with a single command. This guide covers installing Helm, using existing charts, writing your own chart for a custom app, and chart versioning — all tested on DomainIndia VPS running k3s.
Q
Helm or Kustomize?
Helm — templating, versioning, dependencies, repositories. Kustomize — overlays, no templating, in-tree with kubectl. Helm wins for 3rd-party apps; Kustomize for your own simple overlays.
Q
Do I need Helm for small k3s deployments?
For 1-2 services — probably not. For 5+ services with shared config — yes.
Q
Can I use Helm without a Kubernetes cluster?
Yes — helm template renders YAML files you can kubectl-apply manually. But you lose release tracking.
Q
Chart registry options?
Artifact Hub (discovery), OCI registries (Docker Hub, GHCR, Harbor), Bitnami, self-hosted ChartMuseum, GitHub Pages.
Q
Breaking changes across Helm versions?
Helm 3 (current) removed Tiller — client-only. Helm 2 is dead. All tutorials should target Helm 3+.
Run Helm on your k3s cluster on DomainIndia VPS. Get a VPS
Was this article helpful?
Your feedback helps us improve our documentation
Still need help? Submit a support ticket