Kubernetes

Helm Charts for Beginners — Deploying with Charts on k3s and Kubernetes

By Domain India Team · DomainIndia EngineeringPublished 9 min read
Knowledge base article
Contents (14 sections)

Once you run more than a couple of services on Kubernetes, hand-editing YAML stops scaling. Helm packages those files into versioned, configurable charts you can install, upgrade and roll back with one command.

Key takeaways

Helm is the package manager of Kubernetes. Instead of writing a dozen YAML files for an app, you install one chart with a single command and keep your settings in a values file. This guide covers installing Helm, using existing charts, writing your own chart, secrets, dependencies and GitOps. The commands work on k3s, which runs well on a single self-managed VPS, and on any other Kubernetes cluster.

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 a database" declared once in Chart.yaml
  • Repositories — thousands of pre-built charts listed on Artifact Hub

Install Helm

Use the install script or package from the official Helm documentation (helm.sh, "Installing Helm"). Read a script before piping it to a shell. Then check:

bash
helm version

Helm runs on any machine that can reach your cluster with a kubeconfig. On a k3s server, point Helm at the k3s config:

bash
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml

The everyday commands in this guide (install, upgrade, rollback, template, lint) are the same on current Helm releases. Check the release notes when you move to a new major version.

Pattern 1 — Install existing charts

Add a repository:

bash
helm repo add podinfo https://stefanprodan.github.io/podinfo
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

podinfo is a small demo web app that is handy for learning Helm. Many charts are also published to OCI registries, and you can install those directly with an oci:// address instead of adding a repository.

Search:

bash
helm search repo podinfo
# NAME             CHART VERSION  APP VERSION  DESCRIPTION
# podinfo/podinfo  6.x.x          6.x.x        Podinfo Helm chart for Kubernetes

Install:

bash
helm install demo podinfo/podinfo \
    --namespace demo \
    --create-namespace \
    --set replicaCount=2

Check:

bash
helm list --all-namespaces
kubectl get pods -n demo

Uninstall:

bash
helm uninstall demo -n demo

That's it: an app running in Kubernetes with a handful of commands.

Pattern 2 — Customise via values.yaml

Charts accept configuration. Instead of --set flags, use a values file:

bash
helm show values podinfo/podinfo > my-values.yaml

Edit my-values.yaml:

yaml
replicaCount: 2
resources:
  requests:
    cpu: 50m
    memory: 64Mi
  limits:
    memory: 256Mi

Keys differ from chart to chart; always start from the chart's own helm show values output.

Install with values:

bash
helm install demo podinfo/podinfo -f my-values.yaml -n demo --create-namespace

Keep my-values.yaml in git (without secrets) for reproducible deployments.

Pattern 3 — Upgrade in place

bash
# Change something in my-values.yaml
helm upgrade demo podinfo/podinfo -f my-values.yaml -n demo

Helm computes the diff, applies only what changed, keeps revision history.

Rollback to previous version:

bash
helm history demo -n demo
# REVISION  UPDATED  STATUS      CHART
# 1         ...      superseded  podinfo-6.x.x
# 2         ...      superseded  podinfo-6.x.x
# 3         ...      deployed    podinfo-6.x.x

helm rollback demo 2 -n demo

Pattern 4 — Write your own chart

bash
helm create myapp

Creates:

code
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        # k3s ships Traefik as its ingress controller
  host: myapp.yourcompany.com
  tls: true                 # adjust templates/ingress.yaml to match these keys

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

env:
  NODE_ENV: "production"
  LOG_LEVEL: "info"
# Secrets (DATABASE_URL, JWT_SECRET) go in a Kubernetes Secret, not here. See Pattern 5.

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 over a list of {name, value} items
{{- range .Values.extraEnv }}
- name: {{ .name }}
  value: {{ .value | quote }}
{{- end }}

# Include helper
{{- include "myapp.fullname" . }}

# Default values
image: {{ .Values.image.repository | default "busybox" }}

Test your chart

bash
# Render templates locally without touching the cluster
helm template myapp ./myapp

# Dry-run against the cluster
helm install myapp ./myapp --dry-run

# 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 a secrets store such as HashiCorp Vault or OpenBao, or a cloud secrets manager. Your chart references the Kubernetes Secret by name; the values live outside git.

Option B — Sealed Secrets:

bash
kubeseal --format yaml < secret.yaml > sealed-secret.yaml
# sealed-secret.yaml is safe to commit; only the controller in your cluster can decrypt it

Option C — Manually via --set:

bash
kubectl create secret generic myapp-secrets -n production \
    --from-literal=JWT_SECRET="$JWT_SECRET"

Then reference myapp-secrets from the Deployment with envFrom or secretKeyRef. Avoid --set for secrets: the values are stored in the Helm release record and are visible to anyone who can run helm get values.

Pattern 6 — Dependencies

Chart.yaml:

yaml
dependencies:
  - name: postgresql
    version: "x.y.z"                 # pin an exact version
    repository: "oci://registry.example.com/charts"
    condition: postgresql.enabled

  - name: valkey
    version: "x.y.z"
    repository: "https://charts.example.com"
    condition: valkey.enabled

Download deps:

bash
helm dependency update

Pick dependency charts that are actively maintained and whose container images you can pull. Some popular public chart catalogues changed their free-image terms in 2025, so check a chart's current status on Artifact Hub before you depend on it. For production PostgreSQL on Kubernetes, many teams use an operator such as CloudNativePG instead of a plain chart.

Install with deps:

bash
helm install myapp ./myapp \
    --set postgresql.enabled=true \
    --set valkey.enabled=true

One chart, whole stack (app + DB + cache).

Pattern 7 — Publish your chart

bash
helm package ./myapp
# Creates myapp-0.1.0.tgz

# Option 1: push to an OCI registry (GHCR, Docker Hub, Harbor)
helm push myapp-0.1.0.tgz oci://ghcr.io/yourorg/charts

# Option 2: a static chart repository (for example GitHub Pages)
helm repo index ./charts-repo

Others can:

bash
helm install myapp oci://ghcr.io/yourorg/charts/myapp --version 0.1.0
# or, for a static repository:
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
  namespace: argocd
spec:
  project: default
  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

Pinning chart versions loosely
A range or * in production breaks on surprise upgrades. Pin exact versions.
Secrets in values.yaml committed to git
Rotate every exposed secret and move them to a secrets manager or Sealed Secrets.
Template errors found too late
Run helm lint and helm template before every release.
Forgetting to update dependencies
Run helm dependency update after changing Chart.yaml.
Namespace not created
Use --create-namespace.
helm upgrade without --install
It fails if the release doesn't exist yet. helm upgrade --install handles both cases.

FAQ

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.

Do I need Helm for small k3s deployments?

For one or two services, plain manifests are fine. Helm pays off once you have several services with shared configuration, or when you install third-party software that ships as a chart.

Can I use Helm without a Kubernetes cluster?

Yes — helm template renders YAML files you can kubectl-apply manually. But you lose release tracking.

Chart registry options?

Artifact Hub for discovery; OCI registries (GHCR, Docker Hub, Harbor) for storage; or a static repository on GitHub Pages or ChartMuseum.

Breaking changes across Helm versions?

Helm 2, with its in-cluster Tiller component, is long dead; ignore any tutorial that installs Tiller. Current Helm is client-only. Read the release notes before moving to a new major version, and test your charts with it first.

Can I run k3s and Helm on Domain India hosting?

On a Domain India VPS, yes: it is self-managed with full root access on KVM, so you can install k3s and Helm yourself. Shared hosting and the App Platform can't run Kubernetes.

Running this on Domain India

  • VPS: self-managed, full root access, KVM virtualisation. You can install k3s and Helm on it; you are responsible for installing, securing and updating the cluster.
  • Backups: VPS plans include no backups or snapshots. Keep your charts and values in git, and back up persistent volumes and the k3s datastore yourself.
  • Shared hosting and App Platform: they can't run Kubernetes. For a single web app without a cluster, the App Platform deploys from GitHub or with a deploy token.

Ready to run your own cluster? Choose a Domain India VPS, set up CI with the GitHub Actions deploy guide, or open a ticket with hosting questions.

Run k3s on your own VPS

A self-managed KVM VPS with full root access, ready for k3s, Helm and your own charts.

View VPS plans

Was this article helpful?

Your answer helps us decide what to improve next.

Still need help? Open a support ticket and our team will reply.

Prefer an app? Add this site to your home screen.Get the app