Helmizer
TLDR
Generates a kustomization.yaml file, optionally providing the ability to run commands (e.g. helm template) on your OS prior to generating a kustomization, and will compose the kustomization fields that deal with file paths (e.g. resources) with glob-like features, as well as pass-through all other kustomization configuration properties. No need to explicitly enumerate every file to be 'kustomized' individually.
helmizer takes a config file as input, telling Helmizer if you want to run any commands. Then if you give it one or more directories for crds/components/patchesStrategicMerge/resources, it will recursively lookup all of those files and render them into your kustomization.yaml. Want to skip including one file like templates/secret.yaml? Just add the relative path to helmizer.ignore to helmizer.yaml.
Thou shall not glob, said the kustomize developers (thus far).
Helmizer takes various inputs from a YAML config file (helmizer.yaml by default) and constructs a kustomization file from those inputs. It can run a sequence of commands before rendering the kustomization file.
Instead of manually entering the paths to resources in a kustomization file, this tool will walk any number of directories containing resources and populate the kustomization with these resources. Or only pull in individual files, it's your choice.
I began transitioning my helm charts to local manifests via helm template, which were then applied to the cluster separately via Kustomize. I didn't enjoy having to manually manage the relative paths to files in the kustomization. I wanted a repeatable process to generate Kubernetes manifests from a helm chart, and tack on any patches or related resources later with a single command. Thus, helmizer. But Helm is in no way required to make this tool useful - have it walk your raw manifests as well. This is just a wrapper that allows combining steps, and glob-like behavior, to managing kustomization.yaml files.
Usage: helmizer [--log-format LOG-FORMAT] [--log-level LOG-LEVEL] [--log-colors] [--api-version API-VERSION] [--dry-run] [--kustomization-path KUSTOMIZATION-PATH] [--quiet-commands] [--quiet-helmizer] [--skip-commands] [--skip-postcommands] [--skip-precommands] [--stop-on-error] [--config-glob CONFIG-GLOB] [CONFIGFILEPATH]
Positional arguments:
CONFIGFILEPATH Path to Helmizer config file (optional if --config-glob is set)
Options:
--log-format LOG-FORMAT
Set log format: plain or JSON [default: plain]
--log-level LOG-LEVEL, -l LOG-LEVEL
Set log level: INFO, DEBUG, ERROR, WARNING [default: INFO]
--log-colors Enables color in the log output [default: true]
--api-version API-VERSION
Set the API version for the kustomization.yaml file [default: kustomize.config.k8s.io/v1beta1]
--dry-run Don't write the kustomization.yaml file. This does not affect pre/post commands [default: false]
--kustomization-path KUSTOMIZATION-PATH
Set the path to write kustomization.yaml file [default: .]
--quiet-commands, -q Don't output stdout/stderr for pre and post command sequences [default: false]
--quiet-helmizer Don't output logs or the kustomization [default: false]
--skip-commands Skip executing pre and post command sequences [default: false]
--skip-postcommands Skip executing the post-command sequence [default: false]
--skip-precommands Skip executing the pre-command sequence [default: false]
--stop-on-error Stop execution on first error [default: true]
--config-glob CONFIG-GLOB
Glob pattern(s) for Helmizer config files; supports ** and comma-separated values
--help, -h display this help and exit
--version display version and exit
Run against multiple configs with --config-glob instead of shelling out to find. Example:
helmizer --config-glob "**/helmizer.yaml"You can also pass comma-separated patterns, for example: --config-glob "apps/**/helmizer.yaml,clusters/**/helmizer.yaml".
- Example
helmizer.yamlconfig file. Thehelmcommand is invoked before the content forkustomization.yamlis generated. Any number of commands can be added here.
helmizer:
apiVersion: "kustomize.config.k8s.io/v1beta1"
dryRun: false
ignore: []
kustomizationPath: "."
preCommands:
- command: helm
args:
- '-n'
- cert-manager
- template
- cert-manager
- '--output-dir'
- .
- '--include-crds'
- '--skip-tests'
- '--version'
- 1.19.2
- jetstack/cert-manager
postCommands:
- command: pre-commit
args:
- run
- '-a'
- '||'
- 'true'
quietCommands: false
skipAllCommands: false
skipPostCommands: false
skipPreCommands: false
kustomize:
commonAnnotations: {}
commonLabels: {}
configMapGenerator: []
crds: []
generatorOptions: {}
images: []
namePrefix: ""
namespace: ""
nameSuffix: ""
openapi: {}
patches: []
patchesJson6902: []
patchesStrategicMerge: []
replacements: []
replicas: []
resources: []
secretGenerator: []
vars: []Click to expand this bit to see the above, but with explanations for the configuration properties
helmizer:
dryRun: false # optional - if true, does not write to a filesystem
ignore: [] # optional - list of files/directories to ignore
kustomizationPath: "." # optional - path to write kustomization.yaml
preCommands: # optional - list of commands/args executed serially. Inherits your $PATH
- command: "helm"
args:
- "-n"
- "cert-manager"
- "template"
- "cert-manager"
- --output-dir
- '.'
- --include-crds
- --skip-tests
- --version
- 1.19.2
- jetstack/cert-manager
postCommands: # optional - list of commands/args executed serially. Inherits your $PATH
- command: "pre-commit"
args:
- 'run'
- '-a'
- '||'
- 'true'
quietCommands: false
skipAllCommands: false
skipPostCommands: false
skipPreCommands: false
stopOnError: true
kustomize: # this is essentially an overlay for your eventual kustomization.yaml
buildMetadata: []
commonAnnotations: {}
commonLabels: {}
configMapGenerator: []
crds: []
generatorOptions: {}
helmCharts: []
images: []
labels: []
namePrefix: ""
namespace: ""
nameSuffix: ""
openapi: {}
patches: []
patchesJson6902: []
patchesStrategicMerge: []
replacements: []
replicas: []
resources: []
secretGenerator: []
sortOptions: {}
vars: []curl -L "https://github.com/DaemonDude23/helmizer/releases/download/v0.19.1/helmizer_0.19.1_linux_amd64.tar.gz" -o helmizer.tar.gz && \
tar -xzf helmizer.tar.gz helmizer && \
sudo mv helmizer /usr/local/bin/ && \
rm helmizer.tar.gz && \
sudo chmod +x /usr/local/bin/helmizerUsing the included flake:
# Run without installing
nix run github:daemondude23/helmizer -- helmizer.yaml
# Install to your user profile
nix profile install github:daemondude23/helmizerTo add to a NixOS or home-manager flake configuration:
# flake.nix inputs
inputs.helmizer.url = "github:daemondude23/helmizer";
# In your configuration (NixOS or home-manager)
environment.systemPackages = [
inputs.helmizer.packages.${system}.default
];Two Dockerfiles are available:
Dockerfile: minimal scratch image with justhelmizer.Dockerfile.helm: alpine image withhelmizerplus the Helm binary copied fromdocker.io/alpine/helm:4.1.4.
Minimal:
# Builder stage
FROM ghcr.io/daemondude23/helmizer/helmizer:v0.19.1 AS builder
# Final minimal stage
FROM scratch
COPY --from=builder /usr/local/bin/helmizer /usr/local/bin/helmizerWith Helm:
# Builder stage
FROM ghcr.io/daemondude23/helmizer/helmizer-helm:v0.19.1 AS builder
# Final minimal stage
FROM scratch
COPY --from=builder /usr/local/bin/helm /usr/local/bin/helm
COPY --from=builder /usr/local/bin/helmizer /usr/local/bin/helmizer- Download the Windows version.
- Untar it and put it in your
$PATH.
For greater detail on running from examples (they assumes you've ran helm template, see the resource example)
Input file:
helmizer:
preCommands:
- command: "helm"
args:
- "-n"
- "cert-manager"
- "template"
- "cert-manager"
- --output-dir
- '.'
- --include-crds
- --skip-tests
- --version
- 1.19.2
- jetstack/cert-manager
kustomize:
namespace: cert-manager
resources:
- ./cert-manager/Helmize-ify it:
helmizer ./examples/resources/helmizer.yamlOutput - enumerating the files within the specified directory:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: cert-manager
resources:
- cert-manager/cainjector-deployment.yaml
- cert-manager/cainjector-rbac.yaml
- cert-manager/cainjector-serviceaccount.yaml
- cert-manager/deployment.yaml
- cert-manager/rbac.yaml
- cert-manager/service.yaml
- cert-manager/serviceaccount.yaml
- cert-manager/startupapicheck-job.yaml
- cert-manager/startupapicheck-rbac.yaml
- cert-manager/startupapicheck-serviceaccount.yaml
- cert-manager/webhook-deployment.yaml
- cert-manager/webhook-mutating-webhook.yaml
- cert-manager/webhook-rbac.yaml
- cert-manager/webhook-service.yaml
- cert-manager/webhook-serviceaccount.yaml
- cert-manager/webhook-validating-webhook.yamlWith vscode you can utilize the included launch.json to test these more quickly, or reference for your configuration.
The cert-manager Helm chart is used for examples for its small scope. Here's another.
- buildMetadata
- commonAnnotations
- commonLabels
- configMapGenerator
- crds
- generatorOptions
- helmCharts
- images
- labels
- namePrefix
- namespace
- nameSuffix
- openapi
- patches
- patchesJson6902
- patchesStrategicMerge
- replacements
- replicas
- resources
- secretGenerator
- sortOptions
- vars
Which looks easier to write/maintain through future chart updates for the Prometheus Operator/kube-prometheus-stack, this helmizer.yaml?
helmizer:
preCommands:
- command: "helm"
args:
- "-n"
- "cert-manager"
- "template"
- "cert-manager"
- --output-dir
- '..'
- --include-crds
- --skip-tests
- --validate
- --version
- '1.19.2'
- --values
- 'values.yaml'
- jetstack/cert-manager
kustomize:
namespace: monitoring
resources:
- ./templates/Or this kustomization.yaml which helmizer generated?
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: cert-manager
resources:
- cert-manager/cainjector-deployment.yaml
- cert-manager/cainjector-rbac.yaml
- cert-manager/cainjector-serviceaccount.yaml
- cert-manager/deployment.yaml
- cert-manager/rbac.yaml
- cert-manager/service.yaml
- cert-manager/serviceaccount.yaml
- cert-manager/startupapicheck-job.yaml
- cert-manager/startupapicheck-rbac.yaml
- cert-manager/startupapicheck-serviceaccount.yaml
- cert-manager/webhook-deployment.yaml
- cert-manager/webhook-mutating-webhook.yaml
- cert-manager/webhook-rbac.yaml
- cert-manager/webhook-service.yaml
- cert-manager/webhook-serviceaccount.yaml
- cert-manager/webhook-validating-webhook.yamlHelmizer is available as a GitHub Action. It uses the Dockerfile.helm image, which includes both helmizer and helm, so pre-commands that call helm template work out of the box.
| Input | Required | Default | Description |
|---|---|---|---|
config |
No | helmizer.yaml |
Path to a single helmizer config file, relative to the workspace. |
config_glob |
No | "" |
Glob pattern(s) for helmizer config files. Supports ** and comma-separated values. |
When config_glob is set, the config positional argument is optional — if the default helmizer.yaml doesn't exist at the repo root, it is silently skipped.
Run helmizer against a single config:
- uses: daemondude23/helmizer@v0.19.1
with:
config: path/to/helmizer.yamlRun helmizer against all configs in the repo:
- uses: daemondude23/helmizer@v0.19.1
with:
config_glob: "**/helmizer.yaml"A common pattern: use Renovate or Dependabot to automatically open PRs that bump helm chart versions or container image tags in your helmizer.yaml files, then run helmizer as a second step in that PR to regenerate kustomization.yaml with the updated versions.
Add this workflow to your GitOps/manifests repo (the repo that contains your helmizer.yaml files):
# .github/workflows/helmizer.yaml
name: Regenerate Kustomizations
on:
pull_request:
paths:
- "**/helmizer.yaml"
- "**/values.yaml"
- "**/Chart.yaml"
jobs:
helmizer:
runs-on: ubuntu-latest
# Only run on Renovate or Dependabot PRs
if: >-
github.actor == 'renovate[bot]' ||
github.actor == 'dependabot[bot]'
permissions:
contents: write
steps:
- name: Checkout PR branch
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Find affected helmizer configs
id: find-configs
run: |
# Get files changed in this PR
changed_files=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
# For each changed file, walk up to find the nearest helmizer.yaml
configs=""
for file in $changed_files; do
dir=$(dirname "$file")
while [ "$dir" != "." ] && [ "$dir" != "/" ]; do
if [ -f "$dir/helmizer.yaml" ]; then
configs="$configs $dir/helmizer.yaml"
break
fi
dir=$(dirname "$dir")
done
# Also check repo root
if [ -f "helmizer.yaml" ] && [ "$dir" = "." ]; then
configs="$configs helmizer.yaml"
fi
done
# Deduplicate
configs=$(echo "$configs" | tr ' ' '\n' | sort -u | tr '\n' ',' | sed 's/,$//')
echo "configs=$configs" >> "$GITHUB_OUTPUT"
echo "Found helmizer configs: $configs"
- name: Run Helmizer
if: steps.find-configs.outputs.configs != ''
uses: daemondude23/helmizer@v0.19.1
with:
config_glob: ${{ steps.find-configs.outputs.configs }}
# Optional: run pre-commit hooks on regenerated files
# Uncomment the following step if your repo uses pre-commit
# - name: Run pre-commit hooks
# if: steps.find-configs.outputs.configs != ''
# uses: pre-commit/action@v3.0.1
- name: Commit regenerated kustomization files
if: steps.find-configs.outputs.configs != ''
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A "**/kustomization.yaml"
git diff --staged --quiet || git commit -m "chore: regenerate kustomization files"
git push- Renovate/Dependabot opens a PR bumping a helm chart version or container image tag in a
helmizer.yaml(orvalues.yaml, etc.) - This workflow triggers on that PR because a helmizer-related file changed
- The workflow finds the nearest
helmizer.yamlby walking up the directory tree from each changed file — only the affected chart directories are processed, not the entire repo - Helmizer runs against only those configs — executing pre-commands (e.g.
helm template) and regeneratingkustomization.yamlfiles - (Optional) Pre-commit hooks run to lint/format the regenerated files
- The updated kustomization files are committed back to the PR branch
Renovate can detect version bumps in the kustomization.yaml files that helmizer generates (e.g. helmCharts versions, images tags). When it finds a newer version, it opens a PR updating those fields. The helmizer workflow then re-runs to regenerate the kustomization consistently.
For repos that also use Chart.yaml or values.yaml, Renovate can detect helm chart versions there too.
Example .github/renovate.json for your GitOps repo:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base"],
"docker": {
"enabled": true
},
"packageRules": [
{
"enabled": true,
"managers": ["helm-requirements", "helm-values"],
"matchFiles": ["**/Chart.yaml"]
},
{
"enabled": true,
"managers": ["kustomize"],
"matchFiles": ["**/kustomization.yaml"]
}
]
}Dependabot does not natively parse kustomization.yaml for helm chart versions, but it can detect Docker image tags in Dockerfiles and GitHub Actions versions. For helm chart version bumps specifically, Renovate is the better choice as it has native support for helm and kustomize version detection.
Example .github/dependabot.yml:
version: 2
updates:
- package-ecosystem: docker
directory: /
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: /
schedule:
interval: weeklyThe example workflow above restricts runs to Renovate and Dependabot PRs via the if condition. To run helmizer on all PRs that touch helmizer-related files (including manual PRs), remove the if block:
jobs:
helmizer:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
# ... same steps as aboveIn GitLab, there is no action.yml equivalent — instead, use the helmizer Docker image directly in your .gitlab-ci.yml pipeline.
# .gitlab-ci.yml
helmizer:
image: ghcr.io/daemondude23/helmizer/helmizer:v0.19.1
script:
- helmizer --config-glob "**/helmizer.yaml"If your helmizer configs use helm template in pre-commands, use the helmizer-helm image which bundles helm:
helmizer:
image: ghcr.io/daemondude23/helmizer/helmizer-helm:v0.19.1
script:
- helmizer --config-glob "**/helmizer.yaml"The same pattern as GitHub — Renovate opens a merge request bumping versions, then a pipeline job runs helmizer and commits the regenerated files back to that MR branch.
Add this to your GitOps/manifests repo:
# .gitlab-ci.yml
stages:
- regenerate
helmizer:
stage: regenerate
image: ghcr.io/daemondude23/helmizer/helmizer-helm:v0.19.1
rules:
# Only run on Renovate MR branches
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^renovate\//
changes:
- "**/helmizer.yaml"
- "**/values.yaml"
- "**/Chart.yaml"
before_script:
- git config user.name "gitlab-ci"
- git config user.email "gitlab-ci@${CI_SERVER_HOST}"
- git remote set-url origin "https://gitlab-ci-token:${HELMIZER_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
- git fetch origin "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"
- git checkout "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"
script:
- |
# Find helmizer configs affected by this MR
changed_files=$(git diff --name-only "origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}...HEAD")
configs=""
for file in $changed_files; do
dir=$(dirname "$file")
while [ "$dir" != "." ] && [ "$dir" != "/" ]; do
if [ -f "$dir/helmizer.yaml" ]; then
configs="$configs $dir/helmizer.yaml"
break
fi
dir=$(dirname "$dir")
done
if [ -f "helmizer.yaml" ] && [ "$dir" = "." ]; then
configs="$configs helmizer.yaml"
fi
done
configs=$(echo "$configs" | tr ' ' '\n' | sort -u | tr '\n' ',' | sed 's/,$//')
echo "Found helmizer configs: $configs"
if [ -n "$configs" ]; then
helmizer --config-glob "$configs"
# Optional: run pre-commit hooks on regenerated files
# Uncomment the following lines if your repo uses pre-commit
# pip install pre-commit
# pre-commit run --all-files || true
git add -A "**/kustomization.yaml"
git diff --staged --quiet || git commit -m "chore: regenerate kustomization files"
git push origin "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"
fiHELMIZER_TOKEN: Create a project or group access token withwrite_repositoryscope. Add it as a CI/CD variable. The defaultCI_JOB_TOKENtypically cannot push to protected branches.- Branch protection: Ensure the
renovate/*branch pattern is not protected, or allow the token to push to it. - Renovate on GitLab: Renovate supports GitLab natively. You can run it as a scheduled pipeline or use the Mend-hosted Renovate app. The same
renovate.jsonconfig from the Renovate Setup section works on both GitHub and GitLab.
To run on all merge requests (not just Renovate), simplify the rules:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- "**/helmizer.yaml"
- "**/values.yaml"
- "**/Chart.yaml"All kustomize configuration options which are not deprecated by kustomize are supported. See here for reference.
