Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Helm Charts Explained: Package and Deploy Kubernetes Apps

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.

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.

Chart DirectoryChart.yamlvalues.yamltemplates/*.yamlcharts/ (deps)Helm Client (v3)Template Engine+ Values Merge→ Valid K8s YAMLKubernetes ClusterRelease SecretDeployments / SvcConfigMaps / PVCClient-side rendering + Atomic Release Storage
Helm Charts Explained: Package and Deploy Kubernetes Apps — Chart structure flows through client-side rendering to produce atomic cluster releases

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 version field 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.yaml and running helm dependency update to 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

  1. Flat over nested: Prefer image.tag over containers.app.image.tag unless nesting provides genuine semantic grouping. Deep nesting makes overrides verbose and error-prone.
  2. Type explicitly: Always quote strings that look like numbers or booleans. tag: "1.20" prevents YAML parsers from interpreting it as a float.
  3. Document inline: Add comments above each key explaining its purpose, valid range, and default behavior. Future maintainers (including yourself) will thank you.
  4. 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.

Rev 1 (v0.2.0)DEPLOYEDRev 2 (v0.3.0)DEPLOYEDRev 3 (v0.3.1)FAILEDRev 4 (rollback)Restores Rev 2helm rollback legal-portal 2Release Secrets in Namespacesh.helm.release.v1.legal-portal.v1sh.helm.release.v1.legal-portal.v2sh.helm.release.v1.legal-portal.v3 (failed)
Helm upgrade and rollback lifecycle — each revision stored atomically enables instant recovery without template re-rendering

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.

AspectHelm v3 (2026 Standard)Raw kubectl / Kustomize
State TrackingAutomatic revision history in-clusterNone (external GitOps required)
TemplatingFull Go templates with helpersPatch-based overlays only
RollbackInstant single commandManual git revert + reapply
DependenciesBuilt-in versioned resolutionManual vendoring or external tools
Secret ManagementEncrypted plugins (SOPS/AGE)External secret operators needed
Learning CurveModerate (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.

Git RepoChart Source+ Encrypted ValsCI PipelineLint + TestPackage + SignOCI RegistryImmutable ArtifactSigned + VersionedCD ControllerPull by Digesthelm upgrade --atomicSecurity GatesSOPS Decrypt • Cosign Verify • Policy Check • Atomic Flag
Secure Helm CI/CD pipeline — signed OCI artifacts pulled by digest prevent tampering and ensure reproducible deployments

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 latest or 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 --debug against 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.

Frequently Asked Questions

A Helm chart is a collection of files describing related Kubernetes resources. It uses Go templates to parameterize manifests, allowing you to package, version, and deploy applications consistently across different environments without rewriting YAML for every cluster or namespace.

Run curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash to install the latest stable Helm 3.x binary. Verify with helm version. This method avoids outdated apt packages and ensures compatibility with current Kubernetes clusters and chart repositories used in production.

Helm excels at packaging reusable applications with complex configuration logic and dependency management. Kustomize suits overlay-based environment differences without templating. In my experience deploying Laravel apps via GitLab CI, Helm wins for distributable software while Kustomize fits internal infrastructure tweaks where template complexity adds unnecessary maintenance burden.

Helm itself is free open-source software. Costs arise from engineering time learning templating and maintaining charts. For Nepal agencies, budget Rs 15,000–30,000 (~USD 110–220) per chart setup including testing. Ongoing costs depend on chart complexity and team familiarity with Go templating syntax and Kubernetes resource patterns.

Run helm create mychart to scaffold a standard directory structure. Edit templates/deployment.yaml and values.yaml to match your application. Test locally with helm template mychart ./mychart before installing. Remove unused example resources immediately to avoid confusion. Validate against your target Kubernetes version using helm lint during development.

Never commit plaintext secrets to values.yaml. Use external secret operators like External Secrets Operator or Sealed Secrets to inject credentials at runtime. For Nepal projects handling payment gateway keys for eSewa or Khalti, I store API credentials in AWS Secrets Manager and reference them via service accounts. This prevents accidental exposure in Git history or chart repositories.

Yes, use pre-install and pre-upgrade hooks to run php artisan migrate within a Job or Pod. Define hook weights to control execution order. In production Laravel deployments, I wrap migrations in idempotent scripts that check migration status first. Always test rollback scenarios separately since failed migrations can leave databases in inconsistent states requiring manual intervention.

Maintain separate values files like values-staging.yaml and values-production.yaml rather than conditional logic inside templates. Pass them during installation with helm install -f values-production.yaml. This keeps charts generic and testable. On client projects, this pattern reduced deployment errors significantly compared to embedding if-else blocks throughout templates for region-specific endpoints or resource limits.

Most errors stem from undefined variables, incorrect indentation, or missing required values. Use helm template --debug to inspect rendered output before applying. Check that all referenced .Values paths exist in values.yaml. In my experience, 90% of issues come from typos in nested value references or forgetting default values for optional parameters introduced during refactoring.

Configure Deployment strategy type RollingUpdate with maxSurge and maxUnavailable in your chart. Use helm upgrade --atomic --wait to ensure rollback on failure. Set readiness probes correctly so traffic shifts only after pods are healthy. For stateful workloads like MySQL, plan upgrades carefully since rolling restarts may cause brief unavailability depending on replication topology and persistent volume reattachment timing.

Use OCI-compliant registries like Harbor, AWS ECR, or GitHub Container Registry instead of legacy HTTP repositories. Push charts with helm push mychart oci://registry.example.com/charts. This integrates with existing container registry authentication and access controls. For Nepal teams, self-hosted Harbor on local infrastructure provides air-gapped capability when internet bandwidth makes pulling from global registries unreliable during deployments.

Run helm status RELEASE_NAME to see resource states and hook outcomes. Use kubectl describe pod POD_NAME for events and logs. Check helm history RELEASE_NAME for previous revision details. If atomic flag was set, automatic rollback already occurred. In practice, most failures trace to misconfigured resource requests, missing ConfigMaps, or RBAC permissions preventing Jobs from executing during hooks.

Public charts may contain hardcoded credentials, excessive RBAC permissions, or outdated base images with known CVEs. Always review templates before installing third-party charts. Pin specific versions rather than using latest tags. Scan container images with Trivy. On legal-tech portals handling sensitive documents, I audit every dependency chart's security context and network policies before adoption to prevent lateral movement vectors.

Add helm commands in .gitlab-ci.yml deploy stages. Store kubeconfig and registry credentials as CI/CD variables. Use helm upgrade --install --atomic --timeout 5m0s for idempotent deploys. Commit built frontend assets as artifacts since build servers lack Node.js. On sister sites sharing Deployer workflows, we migrated to Helm for staging environments while keeping traditional deployment for production until full validation completed.

Skip Helm for simple static sites, single-container apps, or teams new to Kubernetes where raw kubectl suffices. The templating overhead isn't justified below certain complexity thresholds. For small WordPress sites on shared hosting, traditional deployment remains faster. Adopt Helm when managing multiple similar deployments, distributing software externally, or requiring sophisticated lifecycle hooks that plain manifests cannot express cleanly.

Share this article

Quick Contact Options
Choose how you want to connect me: