The YAML problem Helm solves
Deploying a non-trivial app to Kubernetes means writing a pile of manifests: a Deployment, a Service, an Ingress, a ConfigMap, a Secret, maybe an HPA and a PVC. Now deploy the *same* app to dev, staging, and prod — each needs different replica counts, image tags, resource limits, and hostnames. You end up copy-pasting near-identical YAML and hand-editing values, which is exactly how environments drift and break.
Helm is the package manager for Kubernetes. It treats a set of manifests as a single, parameterized, versioned unit — a chart — that you can install, upgrade, roll back, and share.
The core concepts
- Chart — a package of templated Kubernetes manifests plus metadata. The unit you install.
- Values — the configuration inputs (
values.yaml) that fill in the templates. This is how one chart serves many environments. - Release — a specific installation of a chart into a cluster, with a name and a revision history.
- Repository — a place to host and share charts.
The mental model: chart + values = rendered manifests, applied as a named, versioned release.
A chart's anatomy
mychart/
Chart.yaml # name, version, description
values.yaml # default configuration
templates/
deployment.yaml # templated manifests
service.yaml
ingress.yaml
_helpers.tpl # reusable template snippetsTemplates are Go-templated YAML. Instead of hardcoding values, you reference them:
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-app
spec:
replicas: {{ .Values.replicaCount }}
template:
spec:
containers:
- name: app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
resources:
{{- toYaml .Values.resources | nindent 12 }}With values.yaml:
replicaCount: 2
image:
repository: registry.example.com/app
tag: "1.4.0"
resources:
requests: { cpu: 250m, memory: 256Mi }Now the same chart deploys differently per environment just by overriding values.
The commands that make it worth it
# Install a chart as a named release
helm install myapp ./mychart --values prod-values.yaml
# Upgrade in place (e.g. new image tag)
helm upgrade myapp ./mychart --set image.tag=1.5.0
# See revision history
helm history myapp
# Roll back to a previous revision — the killer feature
helm rollback myapp 3
# Preview rendered manifests without applying (great for review)
helm template myapp ./mychart --values prod-values.yaml
# Uninstall cleanly
helm uninstall myappThe rollback is the headline. Because Helm tracks each release as a revision, a bad upgrade is one command away from reverting — it re-applies the previous known-good manifests.
Why charts beat raw kubectl apply
| Concern | Raw kubectl apply | Helm |
|---|---|---|
| Parameterization | Copy/paste + edit | Values per environment |
| Versioning | Git only | Release revisions in-cluster |
| Rollback | Manual re-apply old YAML | helm rollback |
| Packaging/sharing | Tarball of YAML | Charts in repos |
| Atomic install/upgrade | Manual | --atomic flag |
| Dependency management | None | Subcharts/dependencies |
Helm 3 architecture (no more Tiller)
If you read old tutorials mentioning Tiller (a privileged in-cluster server), ignore them. Helm 2's Tiller was a security headache. Helm 3 removed it entirely — Helm is now a client-side tool that talks to the Kubernetes API directly using your kubeconfig credentials. Release state is stored as Secrets in the cluster. This is simpler and far more secure.
Dependencies and reuse
Charts can depend on other charts (subcharts). A web app chart might pull in a Redis chart as a dependency, configured through the parent's values. Public chart repositories (like Bitnami's) provide battle-tested charts for common software so you don't reinvent them. Helm also discovers/installs charts referenced as OCI artifacts in registries now, unifying chart distribution with image distribution.
Where Helm fits in a deploy pipeline
Helm shines as the *final* step of a deploy pipeline: build an image, push it to a registry, then helm upgrade the release with the new tag. The chart encodes *how* the app runs; the pipeline supplies *which version* and *which environment values*.
That's precisely how PandaStack ships your app. After rootless BuildKit builds your image in an ephemeral Kubernetes Job pod and pushes it to Google Artifact Registry, a Helm release deploys it to the GKE cluster. The chart carries the Deployment, Service, Ingress (via Kong), HPA, and resource tiers; your chosen compute tier and env vars become values. Rollbacks in the dashboard map to Helm's revision history — "deploy history" is exactly Helm releases under the hood. You never write a chart; the platform owns it.
Practical tips
- Keep environment differences in values files, not in templates. Templates should be environment-agnostic.
- Use
helm templatein CI to lint and review rendered output before applying. - Use
--atomicso a failed upgrade auto-rolls-back instead of leaving a half-applied mess. - Pin chart and dependency versions for reproducibility.
- Don't put secrets in
values.yamlin Git — use a secrets manager or sealed/external secrets.
References
- [Helm official documentation](https://helm.sh/docs/)
- [Helm chart template guide](https://helm.sh/docs/chart_template_guide/)
- [Helm: Three Big Concepts](https://helm.sh/docs/intro/using_helm/)
- [Helm 3 changes (no Tiller)](https://helm.sh/docs/topics/v2_v3_migration/)
- [Artifact Hub (chart repository)](https://artifacthub.io/)
---
Want Helm-powered deploys with rollbacks and deploy history — without writing a single chart? PandaStack builds, pushes, and Helm-deploys your app automatically. Try it free at [dashboard.pandastack.io](https://dashboard.pandastack.io).