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.
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.
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
The value files per release are hosted on my public repo Github - values.
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.
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
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.