Client Area
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
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.
## 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
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