Table of Contents

Using Helmfile to Generate Kubernetes Manifests

Summary: This wiki page shows how I use helmfile to generate Kubernetes manifests for my gitops workflows.
Date: 21 February 2026

In one of my previous wiki pages I used my own helm chart to generate a kubernetes manifest. The chart can be used to directly install the secret provider in a kubernetes cluster, but it is also possible to generate the manifest so it can be used in a gitops workflow. In this wiki page I will show how to use helmfile to generate the manifest, along with some other helm chart releases.

Using Helmfile

Helmfile is a tool that allows you to define and manage your helm charts in a declarative way. It is a great tool for managing your helm charts in a gitops workflow. You can define your helm charts in a helmfile.yaml file and then use the helmfile command to generate the kubernetes manifest. Thera are many options to use, for example you can import values from other files, or separate your helmfiles into multiple independent helmfiles. For now, we'll try to keep it simple with just one helmfile, two environments and 6 releases. By using conditions, we'll disable two of the releases for the tst environment, and only one in the prd environment. Compared to using just helm, we'll also use variables in the values files, which is something that is not possible when using helm directly.

The helmfile

If you're using VS Code, you might need to install the Kubernetes extension and adjust the JSON schema to 'helmfile' to properly recognize helmfile files.

Note that the full, and constantly evolving, helmfile.yaml can be found in my public repo: Github - helmfile.yaml

The helmfile.yaml is the main file and the helmfile application will look for it by default. It lists the environments, default values files, repositories and releases. I've also created a template for the releases to avoid repeating the same values for each release:

# The list of environments managed by helmfile.
environments:
  tst:
    values:
      - gitopsValues/tst.yaml
  prd:
    values:
      - gitopsValues/prd.yaml

values:
  - gitopsValues/default.yaml
 
---
# Chart repositories used from within this state file
# We have our own internal chart registry (see https://wiki.getshifting.com/helmchartsecretprovider#creating_a_helm_repository_in_azure)
# And a few public ones for other charts we use
repositories:
  - name: getshifting
    url: {{ .Values.chartRegistry }}
    oci: true
  - name: influxdata
    url: https://helm.influxdata.com
  - name: argo
    url: https://argoproj.github.io/argo-helm
  - name: prometheus-community
    url: https://prometheus-community.github.io/helm-charts
  - name: grafana
    url: https://grafana.github.io/helm-charts
  - name: ingress-nginx
    url: https://kubernetes.github.io/ingress-nginx
 
# The desired states of Helm releases. Using templates for easy reuse of value files.
templates:
  release_defaults: &release_defaults
    missingFileHandler: Warn
    values:
      - ../values/{{`{{ .Release.Name }}`}}.yaml*
 
# The desired states of Helm releases.
releases:
  - chart: getshifting/azure-secretprovider
    name: azure-secretprovider-grafana
    condition: azure-secretprovider-grafana.enabled
    namespace: shiftops
    version: 0.1.0
    <<: *release_defaults
  - chart: getshifting/azure-secretprovider
    name: azure-secretprovider-certificate
    condition: azure-secretprovider-certificate.enabled
    namespace: shiftops
    version: 0.1.0
    <<: *release_defaults
  - chart: argo/argocd-apps
    name: argocd-apps
    namespace: shiftops
    version: 1.4.1
    condition: argocd-apps.enabled
    <<: *release_defaults
  - chart: influxdata/influxdb
    name: influxdb
    version: 4.12.0
    condition: influxdb.enabled
    namespace: shiftapp
    <<: *release_defaults
  - chart: prometheus-community/kube-prometheus-stack
    name: kube-prometheus-stack
    version: 60.3.0
    condition: kube-prometheus-stack.enabled
    namespace: shiftops
    <<: *release_defaults
  - chart: grafana/grafana
    name: grafana
    version: 9.2.7
    condition: grafana.enabled
    namespace: shiftops
    <<: *release_defaults
  - chart: ingress-nginx/ingress-nginx
    name: ingress-nginx
    version: 4.11.6
    condition: ingress-nginx.enabled
    namespace: shiftops
    <<: *release_defaults

The releases section defines the desired state of the helm releases. For each release we define the chart, name, version, condition and values (from a template). Below I've added some comments to each option to explain what they do:

  # The chart's repository and name in the repository
  - chart: grafana/grafana
    # The name of the release. This is the name that will be used to identify the release in helmfile and helm commands.
    name: grafana
    # The version of the chart to use. This is optional, if not specified, the latest version will be used.
    version: 9.2.7
    # The condition to determine whether the release should be installed or not. This is optional, if not specified, the release will be installed by default. In this case we only want to install grafana if the value grafana.enabled is set to true in the values file.
    condition: grafana.enabled
    # The default values to set for this release. This is optional, if not specified, the default values will be used. In this case we want to use the same default values for all releases, so we define them in a template and then reference the template here.
    <<: *release_defaults

Release value files

The value files per release are hosted on my public repo Github - values.

Additional value files

As you can see in the helmfile.yaml, there is one valuefile called default.yaml that is in use for all releases. We'll use it to define the default setting whether a release is enabled as well as some of the chart values as well as some variables:

# Default settings for releases
azure-secretprovider-grafana:
  enabled: true
azure-secretprovider-certificate:
  enabled: true
argocd-apps:
  enabled: true
influxdb:
  enabled: true
kube-prometheus-stack:
  enabled: true
grafana:
  enabled: true
ingress-nginx:
  enabled: false
 
# Helmfile global variables
chartRegistry: acrshift.azurecr.io/helm
dockerRegistry: acrshift.azurecr.io/docker
imagePullSecrets: []
namespacePrefix: shift
argocd:
  projectPrefix: shift-
  repoUrl: https://github.com/getshifting/getshifting.git
  excludeManifests: ""
Note: The registry creation is described in the creating_a_helm_repository_in_azure.

Render

We'll use the following script to render the helmfiles we need. Start if for example like this:

One release for one environment: ./render.sh -e prd -r argocd-apps

All releases in one environment: ./render.sh -e prd

#!/bin/bash
 
set -e
set -o pipefail
 
values_arg=""
 
helpFunction() {
  echo ""
  echo "Usage: $0 -e <environment> -r <release> -v <values>"
  exit 1
}
 
while getopts e:r:v: flag
do
  case $flag in
    e) environment=$OPTARG;;
    r) release=$OPTARG;;
    v) values=$OPTARG;;
    ?) helpFunction;;
  esac
done
 
if [ -n "$values" ]; then
  values_arg="--state-values-set=$values"
fi
 
# Add chart repositories defined in state file
helmfile -q -e "$environment" repos
 
function render {
  echo "Rendering to gitopsManifests/$environment"
 
  rm -rf "gitopsManifests/$environment"
  mkdir -p "gitopsManifests/$environment"
 
  release_list=$(helmfile -q -e "$environment" list | awk 'NR!=1 && $3=="true" { print $1 }')
  line_count=$(echo "$release_list" | wc -l)
  echo "Found $line_count releases:"
  echo "$release_list"
 
  counter=0
  echo "$release_list" | while read -r release
  do
    counter=$((counter+1))
    echo "Rendering $counter/$line_count: $release"
    helmfile -q -e "$environment" --selector "name=$release" template --args='--include-crds' --skip-deps $values_arg > "gitopsManifests/$environment/$release.yaml"
  done
}
 
if [[ -n "$release" ]]
then
  echo "Rendering single release: $release"
  rm -f "gitopsManifests/$environment/$release.yaml"
  mkdir -p "gitopsManifests/$environment"
 
  helmfile -q -e "$environment" --selector "name=$release" template --args='--include-crds' $values_arg > "gitopsManifests/$environment/$release.yaml"
else
  render
fi

Use for GitOps

The next step would be to use the generated manifetst in your GitOps workflow. In my opinion, using rendered manifests available in a git repository is very useful for camparison reasons while preparing changes and upgrades, as well as for troublshooting reasons. As you can see, the render script is already prepared for such a workflow. It generated the manifest in a folder called gitopsManifests. On top of that, the argocd-apps release is already prepared to use the generated manifest as source, so you can directly use it in your argocd application. These manifests are also available in the public repo: Github - gitopsManifests.

This wiki has been made possible by: