
August 16, 2026
10 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
Helm Charts Explained: Package and Deploy Kubernetes Apps is the essential mental model for any engineer moving beyond raw YAML manifests. Raw Kubernetes configuration becomes unmanageable at scale; you end up with hundreds of duplicated files, inconsistent environments, and fragile manual deployments. Helm solves this by treating your infrastructure as a parameterized, versioned software artifact rather than static text. This guide covers the practical mechanics of building, testing, and shipping charts in 2026, grounded in real production patterns rather than theoretical documentation.
values.yaml, render environment-specific resources via Go templates, and deploy atomic releases that support instant rollback, upgrade, and dependency management across clusters.If you are managing multiple environments or microservices, ad-hoc kubectl apply commands quickly become a liability. I have seen teams on projects ranging from legal-tech portals to eCommerce platforms struggle because their staging and production configurations drifted apart silently. Adopting a structured packaging approach aligns well with broader modern Laravel architecture best practices where configuration is separated from logic and environment specifics are injected at runtime. Helm brings this same discipline to the infrastructure layer, ensuring that what you test is exactly what you ship.
What Are Helm Charts and How Do They Package Kubernetes Apps?
A Helm chart is a directory containing Go templates, metadata, and default values that collectively describe a Kubernetes application. When you run helm install, the Helm client renders these templates using your supplied values and sends the resulting valid YAML to the cluster API server. Unlike older tools that performed server-side rendering, Helm v3 (current stable in 2026) operates entirely client-side, storing release state directly in Kubernetes Secrets or ConfigMaps within the target namespace.
The core value proposition is reproducibility. A chart tagged v1.4.2 combined with a specific values-prod.yaml will always produce the identical set of Kubernetes resources. This eliminates "it works on my machine" syndrome at the infrastructure level. In practice, I structure charts so that values.yaml contains safe defaults suitable for local development, while environment-specific overrides live in separate files or CI variables. This mirrors how you would handle .env files in a PHP application, keeping secrets out of version control while maintaining a clear audit trail of configuration changes.
Essential Chart Files
- Chart.yaml: Declares name, version (SemVer), appVersion, description, and dependencies. The
versionfield is mandatory and must increment with every change. - values.yaml: Default configuration parameters. Keep this file documented; it serves as the primary interface for other engineers consuming your chart.
- templates/: Contains Go template files (
deployment.yaml,service.yaml,_helpers.tpl). Use helper templates to avoid repeating label selectors and naming conventions. - charts/: Stores dependent sub-charts. Modern Helm prefers declaring dependencies in
Chart.yamland runninghelm dependency updateto pull them from repositories.
How Do You Create and Structure a Production Helm Chart?
Creating a chart starts with helm create my-app, but the generated scaffold requires significant pruning for production use. The default chart includes an ingress, HPA, and notes that most internal applications do not need immediately. Strip it down to the essentials first: Deployment, Service, and ConfigMap. Add complexity only when the application demands it. On a recent legal-tech portal deployment, we started with just three template files and added Redis and worker deployments later as traffic grew, avoiding premature abstraction.
# Chart.yaml - Minimal production starting point
apiVersion: v2
name: legal-portal
description: Nepal legal services portal
type: application
version: 0.3.0 # Chart version - increment on ANY change
appVersion: "2.1.0" # Application container tag
dependencies:
- name: postgresql
version: "15.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled Template hygiene matters enormously. Always use the _helpers.tpl pattern for labels and selectors. Hardcoding strings like app: my-app in multiple templates causes subtle bugs during upgrades when selectors mismatch. Instead, define named templates once and reference them everywhere.
{{/* _helpers.tpl */}}
{{- define "legal-portal.labels" -}}
helm.sh/chart: {{ include "legal-portal.chart" . }}
{{ include "legal-portal.selectorLabels" . }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "legal-portal.selectorLabels" -}}
app.kubernetes.io/name: {{ include "legal-portal.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }} When integrating with CI/CD pipelines, treat chart linting as a gate. Run helm lint ./chart and helm template ./chart --debug before pushing. I have caught countless syntax errors and undefined variable references this way that would otherwise fail silently during deployment. For teams working on CI/CD pipeline setups, adding a ct lint step using the chart-testing tool catches breaking changes and version bumps automatically.
Values File Best Practices
- Flat over nested: Prefer
image.tagovercontainers.app.image.tagunless nesting provides genuine semantic grouping. Deep nesting makes overrides verbose and error-prone. - Type explicitly: Always quote strings that look like numbers or booleans.
tag: "1.20"prevents YAML parsers from interpreting it as a float. - Document inline: Add comments above each key explaining its purpose, valid range, and default behavior. Future maintainers (including yourself) will thank you.
- Sensible defaults: The chart should install successfully with zero overrides for local development. Production hardening comes via override files, not by making the base chart complex.
How Does Helm Manage Upgrades, Rollbacks, and Dependencies?
Helm tracks every release revision as a Secret in the cluster namespace. When you run helm upgrade, it computes a diff between the current deployed state and the new rendered manifest, applying only the necessary changes. If something breaks, helm rollback <release> [revision] restores a previous known-good state instantly without re-rendering templates. This atomic revision history is what makes Helm viable for production systems where downtime during failed deploys is unacceptable.
Dependency management uses the dependencies block in Chart.yaml. Run helm dependency update to fetch specified versions into the charts/ directory. Always commit the charts/ folder or lock file to version control so builds are reproducible without network access. Conditional dependencies via the condition field let you toggle components like databases or caches per environment without maintaining forked charts.
| Aspect | Helm v3 (2026 Standard) | Raw kubectl / Kustomize |
|---|---|---|
| State Tracking | Automatic revision history in-cluster | None (external GitOps required) |
| Templating | Full Go templates with helpers | Patch-based overlays only |
| Rollback | Instant single command | Manual git revert + reapply |
| Dependencies | Built-in versioned resolution | Manual vendoring or external tools |
| Secret Management | Encrypted plugins (SOPS/AGE) | External secret operators needed |
| Learning Curve | Moderate (Go templating) | Low (pure YAML patches) |
For applications requiring database migrations before deployment, use Helm hooks. Annotate a Job with "helm.sh/hook": pre-upgrade and "helm.sh/hook-delete-policy": before-hook-creation to run migrations atomically before the main deployment proceeds. This pattern is critical for Laravel or Symfony applications where schema changes must precede code deployment. I have used this extensively on legal-tech platforms where document schemas evolve alongside application logic.
How Do You Integrate Helm Charts into CI/CD Pipelines Securely?
Production Helm workflows require automated testing, signing, and publishing. Never deploy directly from a developer workstation. Instead, push charts to an OCI registry (like Harbor, ECR, or GitHub Container Registry) after passing lint and integration tests. Your CD system then pulls the immutable artifact by digest, not tag, preventing supply chain attacks where a tag is overwritten maliciously.
# GitLab CI example - Chart publish stage
publish-chart:
stage: release
image: alpine/helm:3.16
script:
- helm lint ./charts/legal-portal
- helm package ./charts/legal-portal --version ${CI_COMMIT_TAG}
- helm push legal-portal-${CI_COMMIT_TAG}.tgz oci://registry.example.com/charts
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/ Security demands attention. Enable --atomic flag on all production installs and upgrades. This ensures that if any resource fails to become ready, the entire release rolls back automatically instead of leaving the cluster in a half-deployed state. Combine this with readiness probes and startup probes in your templates. Also, never store plaintext secrets in values.yaml. Use the SOPS or AGE encryption plugin to encrypt sensitive values at rest, decrypting only during pipeline execution.
Monitoring releases is equally important. Use helm status <release> and helm history <release> to inspect current state and revision timeline. Integrate these checks into your observability stack. When debugging why a service restarted unexpectedly, correlating pod restart times with Helm revision timestamps often reveals whether a config change triggered the instability. This operational visibility is what separates toy deployments from systems that survive Black Friday traffic or peak filing seasons for legal portals.
Common Production Pitfalls
- Mutable tags: Never use
latestor floating tags in production values. Pin to SHA digests or immutable SemVer tags. - Missing resource limits: Always set CPU/memory requests and limits in templates. Unbounded pods cause node instability under load.
- Ignoring hook weights: When multiple hooks exist, set
"helm.sh/hook-weight"to control execution order explicitly. - Not testing upgrades: Run
helm upgrade --dry-run --debugagainst a staging cluster before every production release.
Practical Next Steps for Helm Adoption
Helm Charts Explained: Package and Deploy Kubernetes Apps gives you the vocabulary and mental model, but mastery comes from iterative practice. Start by converting one non-critical service to a Helm chart. Run it through your existing CI pipeline with lint gates. Deploy to a staging namespace with --atomic enabled. Break it intentionally to verify rollback works. Only then promote to production. This incremental approach reduces risk while building team confidence.
Remember that Helm is a packaging and deployment tool, not a silver bullet. It does not replace proper application design, monitoring, or security practices. But when combined with disciplined engineering, it transforms Kubernetes from a source of operational anxiety into a predictable platform for shipping software. Whether you are deploying a WooCommerce storefront, a legal document portal, or a microservices backend, consistent chart practices pay dividends in reliability and velocity.
If you need help architecting Helm workflows for your Kubernetes infrastructure or integrating them with existing Laravel or PHP applications, reach out to discuss your deployment challenges. I regularly assist teams in Nepal and globally with production-grade Kubernetes packaging strategies that balance idealism with operational reality.

