DEV Community

Enbiya Göral
Enbiya Göral

Posted on

How to Manage Your Helm Charts with Helmfile

Helm is great — until your environments start to multiply…
In this post, I’ll walk you through how to use Helmfile to manage your dev, stg, and prod environments in a clean, modular, and repeatable way.

🎯 Why Helmfile?

In Kubernetes projects, Helm charts are among the most widely used tools for simplifying application management. However, when it comes to managing multiple environments such as dev, staging, and prod, organizing Helm chart files and manually running repetitive commands can quickly become time-consuming and error-prone.

This is exactly where Helmfile comes in. Helmfile allows you to manage multiple Helm releases, environments, and values.yaml files efficiently — all from a single declarative YAML file.

In this post, I’ll walk you through how to bring clarity, security, and sustainability to your Helm chart management using my own helmfile-multi-env repository as a reference.

Whether you’re just getting started or already overwhelmed by environment sprawl in a large project, this guide will offer you a practical and repeatable solution. Step by step, I’ll show you how to manage Helm charts with robust multi-environment support.

📁 Project Structure

While building this example, I used a single-node Kubernetes cluster running on Docker Desktop. Instead of creating separate clusters for each environment (dev, stg, prod), I chose to isolate environments within the same cluster using different namespaces.

By deploying different releases of the same Helm chart, and simply changing the values.yaml and namespace for each environment, I was able to create clear boundaries between them.

For example:

  • The dev-nginx namespace contains an NGINX deployment with configuration tailored for the dev environment.
  • The prod-nginx namespace holds a production-ready configuration of the same chart for the prod environment.

I chose this approach to keep things simple while learning Helmfile. Instead of dealing with the complexity of multi-cluster setups, using namespaces within a single cluster made the process much more approachable — especially for beginners.

📂 Project Folder Structure

├── charts/                    
│   └── nginx/             
│       ├── Chart.yaml      
│       ├── templates/       
│       └── values.yaml      
│
└── helmfile.d/              
    ├── env/                 
    │   ├── dev.yaml       
    │   ├── stg.yaml       
    │   └── prod.yaml      
    │
    └── values/              
        ├── dev/
            └─ [RELEASE_NAME]
                └─ values.yaml
        ├── stg/           
        └── prod/
Enter fullscreen mode Exit fullscreen mode
  • charts/: This is where you place custom Helm charts that go beyond what can be handled with just a values.yaml. If you’ve reached a point where you need custom templates, additional resource definitions, or lifecycle hooks — this is the folder for those charts.
  • helmfile.d/env/: This directory contains definition files for each environment (e.g., dev.yaml, stg.yaml, prod.yaml). When running Helmfile, you specify which environment to use via these files — they control what gets deployed and where.
  • helmfile.d/values/: Environment-specific and release-specific values.yaml files are stored here. Each environment has its own folder (like dev/, stg/, prod/), and within those, each release (e.g., nginx, redis, app1) has its own dedicated values.yaml. This structure ensures clean separation between environments and allows each release to maintain its own configuration.

⚖️ Single File or Modular Structure?

In smaller-scale projects, it might seem practical to define all Helm releases and environment configurations in a single helmfile.yaml file. At first, this approach feels simple and convenient — especially during initial development.

However, as the project grows and multiple environments like dev, stg, and prod come into play, this setup can quickly become hard to manage. A single monolithic file makes it difficult to maintain clear separation between environments, increases the risk of misconfiguration, and becomes a bottleneck for team collaboration.

  • Including environment-specific configurations such as values.yaml, secrets.yaml, or namespace definitions all in a single file can lead to a bloated and hard-to-read helmfile.yaml.

  • As environments grow, conditional logic like {{ if eq .Environment.Name "prod" }} becomes inevitable — reducing readability and increasing the likelihood of human error.

  • Moreover, making changes in a single helmfile.yaml may unintentionally affect other environments. This makes maintenance more difficult and introduces the risk of unwanted surprises during deployments.

🔍 How to Run Helmfile per Environment

In this structure, deploying per environment is done as follows:

helmfile -f helmfile.d/env/dev.yaml apply   # For the dev environment
Enter fullscreen mode Exit fullscreen mode

This command:

  • Uses helmfile.d/env/dev.yaml as the main Helmfile,
  • Properly references paths like values/dev/[RELEASE_NAME]/values.yaml.

To deploy other environments, you simply change the filename and environment:

helmfile -f helmfile.d/env/stg.yaml apply
helmfile -f helmfile.d/env/prod.yaml apply
Enter fullscreen mode Exit fullscreen mode

✅ This approach simplifies environment management by removing the need for complex conditionals inside helmfile.yaml.

🧱 Understanding the Helmfile Structure

Now that we’ve explored the project layout and how to manage files per environment, it’s time to take a closer look at the core building blocks of a Helmfile.

In this section, I’ll break down key components like templates, helmDefaults, repositories, and releases — explaining what they do and how to use them effectively, with practical examples along the way.

📌 What is templates?
In Helmfile, the templates section is used to centrally define shared configuration blocks within your YAML files. It allows you to reuse common settings — such as values, secrets, and missingFileHandler — across multiple releases.

This makes your configuration cleaner, more maintainable, and fully aligned with the DRY (Don’t Repeat Yourself) principle.

Here’s what templates enables you to do:

  • ✅ Centralized management of shared settings — for example, if a values.yaml path changes, you only need to update it in one place.
  • 🧠 Dynamic path generation using templating like {{ .Release.Name }}, allowing each release to automatically reference its own file.
  • 🚨 Early error detection using options like missingFileHandler: Error, which helps catch missing file issues during the rendering phase.

YAML Anchor ve Alias ile Templates
Helmfile’s templates feature leverages YAML anchors (&) and aliases (*) — which are not specific to Helmfile but are core features of YAML itself. These constructs allow you to create reusable configuration blocks and reduce duplication across your files.

  • Anchor (&): Labels a YAML block with a name. For example, &default tags a configuration section as default.
  • Alias (*): References a previously defined anchor. For instance, <<: *default will pull in all the settings defined under the default anchor at that point.

This approach is especially helpful when you have multiple releases that share the same settings, saving you from rewriting the same config repeatedly.

templates:
  default: &default
    missingFileHandler: Error
    values:
      - ../../values/dev/{{`{{ .Release.Name }}`}}/values.yaml
#    secrets:
#      - ../../values/dev/{{`{{ .Release.Name }}`}}/secrets.yaml
Enter fullscreen mode Exit fullscreen mode
  • templates.default: Defines a template named default-settings, and marks it with an anchor &default.
  • missingFileHandler: Error: Ensures that if the specified values.yaml file is missing, Helmfile will throw an error and halt execution.
  • values: Uses a dynamic path to reference a values file. For instance, if the release is named nginx, Helmfile will look for a file at helmfile.d/values/dev/nginx/values.yaml.
  • Commented secrets: You can similarly use a dynamic path for secrets.yaml files if needed.

📝 For more details and usage examples, check out the templates section in the official Helmfile documentation.

This template can then be reused in any release definition like so:

releases:
  - name: nginx
    chart: ./charts/nginx
    <<: *default
Enter fullscreen mode Exit fullscreen mode

Here, <<: *default imports all the settings from the default template into the nginx release.

As a result, the values.yaml file is automatically loaded from the dynamic path: helmfile.d/values/dev/nginx/values.yaml

This eliminates the need to manually define file paths for each release, keeping your configuration clean and consistent across environments.

📌 What is helmDefaults?

The helmDefaults section in Helmfile is used to define default settings that apply to all Helm releases unless specifically overridden. It contains global configuration that determines how Helm commands behave, eliminating the need to repeat these settings for each individual release.

helmDefaults:
  kubeContext: docker-desktop
  timeout: 1200
Enter fullscreen mode Exit fullscreen mode
  • kubeContext: Specifies the Kubernetes context in which this Helmfile will be executed. In this case, it’s set to work with Docker Desktop.
  • timeout: Overrides the default timeout for Helm operations. While the default is 300 seconds, here it’s set to 1200 seconds — which is helpful for deployments that take longer to complete.

📌 What is repositories?
In Helmfile, the repositories section is used to define external or local Helm chart repositories from which your charts will be pulled. Helm fetches charts via these repositories, and this section tells Helmfile exactly where to look for them.

This is especially useful when working with multiple chart sources, as it ensures that each chart is pulled from the correct repository — and allows centralized management of repository URLs.

Example:

repositories:
  - name: stable
    url: https://charts.helm.sh/stable
  - name: bitnami
    url: https://charts.bitnami.com/bitnami
Enter fullscreen mode Exit fullscreen mode

For instance, if you want to deploy the bitnami/nginx chart, you must first define the bitnami repository here. Helmfile will then automatically fetch the chart from the specified source when deploying.

📌 What is releases?
In Helmfile, the releases section is the core structure that defines how Helm charts should be deployed to your Kubernetes cluster. Each release specifies how a particular chart should be configured — including its namespace, values, version, and other settings.

The releases block is one of the most critical components of Helmfile, as it dictates exactly what gets deployed and how.

Here’s a basic example with a single release named nginx:

releases:
  - name: nginx
    <<: *default
    installed: true
    namespace: dev-nginx
    chart: bitnami/nginx
    version: 20.0.5
Enter fullscreen mode Exit fullscreen mode

Breakdown:

  • <<: *default: Inherits all default settings defined earlier in the templates section.
  • installed: true: Ensures this release is installed if it’s not already present.
  • namespace: dev-nginx: Uses a unique namespace composed of the environment (dev) and the app name (nginx).
  • chart: bitnami/nginx: Specifies the Helm chart to use, in this case from the Bitnami repository.
  • version: 20.0.5: Locks the chart to a specific version for consistency and repeatability.

🧠 Conclusion

In this post, we explored how to make Helm chart management more sustainable and modular across multiple environments using Helmfile — step by step.

Instead of using a single helmfile.yaml, we isolated environments through a structured, multi-file setup, simplified repetitive configurations using templates, and made each component easier to manage and reason about.

Helmfile is especially valuable for teams managing many environments and releases. Features like namespace isolation, dynamic values handling, and centralized templating bring clarity and control even to the most complex Helm setups.

If you’re just getting started with Helmfile, the structure presented here can be a solid foundation. Start small, create your own repo, and gradually discover the power of a modular approach.

🙏 I hope you found this post helpful!
Feel free to leave your questions, comments, or suggestions below. Stay tuned 👋

My Repo

Top comments (0)