npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

helmify-kustomize

v1.2.27

Published

`helmify-kustomize` is a cli tool designed to make a Kustomize folder compatible with Helm. This tool allows you to upload (pack) a Kustomize folder into an Helm chart format without manually converting it. This to enjoy both the philosophy of kustomize a

Readme

helmify-kustomize

helmify-kustomize is a cli tool designed to make a Kustomize folder compatible with Helm. This tool allows you to upload (pack) a Kustomize folder into an Helm chart format without manually converting it. This to enjoy both the philosophy of kustomize and the shipping/deployment functionality of helm.

npm version Known Vulnerabilities

TL;DR

You have a standard kustomize folder and you want to convert it to a helm chart, you can do it with this tool. Let's assume this is your kustomize folder structure:

kustomize-folder
└── base
|   ├── kustomization.yaml
|   |   ├── .env
|   |   ├── configmap.yaml
|   |   ├── service.yaml
|   |   └── deployment.yaml
├── overlays
│   ├── dev
│   │   ├── kustomization.yaml
│   │   ├── deployment-patch.yaml
│   │   └── .env
│   └── prod
│       ├── kustomization.yaml
│       ├── deployment-patch.yaml
│       └── .env

You can run the following command to convert it to a helm chart:

npx helmify-kustomize build ./kustomize-folder --chart-name example-service --target ./helm-chart

This will create a helm chart in the helm-chart folder in the target folder ./helm-chart. You can now use the helm chart to deploy your application.

Upgrade the chart with the following command:

helm upgrade --install example-service ./helm-chart

Or Create a package and push it to a chart repository:

helm package ./helm-chart
helm push example-service-0.1.0.tgz oci://<registry>/<repository>

Features

  • Processes each Kustomize overlays and base configurations and outputs Helm-compatible files based on provided templates.
  • Packs all overlays as a single chart.
  • Enables helm shipping functionality on a kustomize folder.
  • Support helm values with kustomize replacements.
  • Built in helm values to enable advanced functionality (Read more below).
  • Overlay filtering - Process only specific overlays
  • NEW: Kustomize Files Integration - Include original kustomization files as Helm template data
  • NEW: Enhanced ConfigMap Parametrization - Full runtime configurability of ConfigMap data

Installation

Easiest, no installation (other then nodejs ) just use it with npx

npx helmify-kustomize build <context> --chart-name example-service --target <targetFolder>

You can install it globally

npm i -g helmify-kustomize
helmify-kustomize build <context> --chart-name example-service --target <targetFolder>

Usage

To use the module, run the following command:

npx helmify-kustomize build <context> --target <targetFolder>

Options

  • --chart-name <chartName> : The chart name to be used in Chart.yaml you can read more on the following section what is a valid char name.
  • --chart-version <chartVersion> : The version of the chart to be used in Chart.yaml.
  • --chart-description <chartDescription> : The description of the chart to be used in Chart.yaml.
  • --target <targetFolder>: Target folder for output files (default: helm-output).
  • -k-[name] * : any flag will be forwarded to the kustomize build command -k-something is converted to -something
  • --k-[name] * : any flag will be forwarded to the kustomize build command --k-something is converted to --something
  • --parametrize <key>=<path> : This flag is used to parametrize .env files into the helm values.
  • --parametrize-configmap <key>=<path> : The flag is used to parametrize the configmap in runtime by the key parameter in the .Values, read more about disableNameSuffixHash
  • --overlay-filter <filter> : Comma-separated list of overlay names to include
  • --include-kustomize-files : Include original kustomization files as template data (default: false)

Example

npx helmify-kustomize build ./kustomize-folder --chart-name example-service --target ./helm-chart

This command processes the Kustomize overlays and base configuration, then outputs the Helm-compatible files to the helm-output directory.

How It Works

The core logic is as follows:

  1. The module reads the overlays and base configuration from the current working directory.
  2. kustomize cli needs to be installed seperatly, helmify-kustomize executes kustomize build to process each overlay and the base configuration.
  3. The output overlays are then rendered wrapped as helm chart templates and written to the target folder as templates, with a single if..else condition to activate the specific overlay template.
  4. You can activate the specific overlay by defining the overlay name in the overlay parameter

Helm Chart Naming Conventions

When naming a Helm chart, there are some limitations and best practices you should follow. Here are the key considerations:

Character Set

  • Chart names must consist of lower case alphanumeric characters (a-z, 0-9) and hyphens (-).
  • They cannot contain spaces or special characters other than hyphens.

Length

  • There is no explicit length limit for chart names, but it is good practice to keep names reasonably short and meaningful.

Start and End

  • Chart names must start with a lower case letter.
  • They must end with a lower case letter or a number.

DNS Compatibility

  • Helm chart names should be DNS-compatible. This means they should follow the conventions used for domain names, which helps avoid issues with tools and services that expect DNS-compatible names.

Uniqueness

  • Ensure that the chart name is unique within your repository to avoid conflicts.

Avoid Reserved Words

  • Avoid using reserved words or names that might conflict with existing tools or services.

Examples

Valid Helm Chart Names

  • my-app
  • nginx-chart
  • example-service

Invalid Helm Chart Names

  • MyApp (uppercase letters)
  • my_app (underscore character)
  • my-app! (special character !)

Example of a Valid Chart.yaml

Here is a snippet of a Chart.yaml file with a valid chart name:

apiVersion: v2
name: my-app
description: A Helm chart for Kubernetes
version: 0.1.0
appVersion: 1.0.0

By following these guidelines, you can ensure that your Helm chart names are valid and compatible with Helm and Kubernetes naming conventions.

Built in helm values

helmify-kustomize comes with a built in helm values file that is used to set the values for the helm chart. The file is named values.yaml and is located in the target folder.

This allows for a lot of flexibility in the helm chart, for example you can set the namespace, namePrefix, nameSuffix, nameReleasePrefix, labels, annotations, images, manifests, resources even after the chart is uploaded to a chart repository.

If you think that something is missing and should be added to the built in helm values, please open an issue or a pull request.

  • Values.overlay : This is the name of the overlay that is been deployed, example overlays/dev or overlays/prod.
  • Values.helmifyPrefix : Customize the location of global helmify configuration. By default, global settings are read from globals, but setting this to another value (e.g., "customGlobals") will read from that location instead.
  • Values.globals.namespace : Override the namespace for all resources (highest priority).
  • Values.globals.defaultNamespace : Default namespace to use when .Release.Namespace is not explicitly set.
  • Values.globals.namePrefix : Prepends the value to the names of all resources and references.
  • Values.globals.nameSuffix : Appends the value to the names of all resources and references.
  • Values.globals.nameReleasePrefix : Prepends the value to the name of the release.
  • Values.globals.labels : Specify the labels in all resources.
  • Values.globals.annotations : Specify the annotations in all resources.
  • Values.globals.patches : Apply targeted patches to specific resources. Allows fine-grained modification of Kubernetes resources based on flexible target selectors.
  • Values.images : Specify the images to be updated in the helm chart, simillar to kustomize images section, see example below.
  • Values.manifests : Specify the manifests to be added to your deployment, these manifests will go through the rest of the pipeline, i.e. they will be affected by the globals and images sections.
  • Values.resources : Specify the resources to be added to your deployment, these resources will be added as is to the deployment they will not go through the rest of the pipeline.

Namespace Resolution

Helmify-kustomize provides a sophisticated three-tier namespace resolution system that ensures all Kubernetes resources get a namespace. The resolution follows this priority order:

Resolution Priority (Highest to Lowest)

  1. globals.namespace - Explicit namespace override (when valid)
  2. .Release.Namespace - User-specified namespace via --namespace flag (when explicitly set)
  3. globals.defaultNamespace - Chart-specific default namespace
  4. .Release.Namespace - Fallback to Helm's namespace (defaults to "default")

How It Works

The namespace resolution logic:

  • First checks if globals.namespace is defined and valid (more than 1 character after trimming)
  • If not, checks if .Release.Namespace was explicitly set by the user (not empty and not "default")
  • If neither, uses globals.defaultNamespace if defined and valid
  • Finally falls back to .Release.Namespace (which defaults to "default" if not specified)

Examples

Example 1: Using defaultNamespace

# values.yaml
globals:
  defaultNamespace: my-app-namespace
# Deploy without specifying namespace
helm install myapp ./chart
# Result: Resources deployed to "my-app-namespace"

# Deploy with explicit namespace (overrides defaultNamespace)
helm install myapp ./chart --namespace production
# Result: Resources deployed to "production"

Example 2: Namespace override hierarchy

# values.yaml
globals:
  namespace: override-namespace      # Highest priority
  defaultNamespace: default-app-ns   # Used only if namespace is not set
# Deploy with any --namespace flag
helm install myapp ./chart --namespace user-specified
# Result: Resources deployed to "override-namespace" (globals.namespace wins)

Example 3: Parent-child chart inheritance

# Parent chart values.yaml
globals:
  defaultNamespace: parent-default

# Child chart can inherit or override
child-chart:
  globals:
    namespace: child-override  # Child overrides parent

Edge Cases

  • Empty strings: Treated as invalid and fall through to next priority
  • Single character namespaces: Treated as invalid (must be >1 character after trimming)
  • Whitespace: All namespace values are trimmed before validation
  • "default" namespace: When .Release.Namespace is "default", it's treated as not explicitly set

Example of what is possilbe to set in the values.yaml file

overlay: overlays/dev
globals:
  namespace: dev              # Explicit override (highest priority)
  defaultNamespace: dev-default  # Fallback when Release.Namespace not set
  namePrefix: dev-
  nameSuffix: -dev
  nameReleasePrefix: dev-
  labels:
    app: dev
  annotations:
    app: dev
  patches:
    - target:
        kind: Deployment
        name: web-app
      ops:
        - op: add
          path: /spec/template/spec/containers/0/env/-
          value:
            name: LOG_LEVEL
            value: debug
    - target:
        labelSelector: "app=web"
      ops:
        - op: add
          path: /metadata/labels/environment
          value: development
  images:
    - image: . # this will catch all images in all deployment
      pullSecrets: # this will add the pull secrets to all pods
        - name: new-pull-secret
    - image: old-image # this will catch all images in all deployment with the old-image name
      newName: new-image
      newTag: new-tag
      digest: new-digest
manifests:
  - kind: Deployment # this will be added to result and go through the rest of the pipeline manipulations
    name: example-deployment
    spec:
      template:
        spec:
          containers:
            - name: example-container
              image: example-image
resources:
  - kind: Deployment # this will be added to result as is
    name: example-deployment
    spec:
      template:
        spec:
          containers:
            - name: example-container
              image: example-image

Example using custom helmifyPrefix location

overlay: overlays/prod
helmifyPrefix: "customGlobals"  # use customGlobals instead of globals
customGlobals:  # all global settings now go here instead of globals
  namespace: production
  namePrefix: prod-
  labels:
    environment: production
    team: platform
  patches:
    - target:
        group: apps
        version: v1
        kind: Deployment
        namespace: production
      ops:
        - op: add
          path: /metadata/labels/release-channel
          value: stable
  images:
    - image: old-image
      newName: prod-image
      newTag: v2.0.0

Example of how to set these values with the helm set command

Here demonstrated only a few of the possible values, but you can set any of the values in the values.yaml file.

# Set explicit namespace override
helm upgrade --install example-service ./helm-chart --set globals.namespace=new-namespace --set globals.namePrefix=new-name-prefix

# Set default namespace (used when --namespace is not specified)
helm upgrade --install example-service ./helm-chart --set globals.defaultNamespace=app-default

# Combine with Helm's --namespace flag
helm upgrade --install example-service ./helm-chart --namespace production --set globals.defaultNamespace=staging
# Result: Uses "production" (explicit --namespace takes precedence over defaultNamespace)

When using custom helmifyPrefix, adjust the paths accordingly:

helm upgrade --install example-service ./helm-chart --set helmifyPrefix=customGlobals --set customGlobals.namespace=new-namespace --set customGlobals.namePrefix=new-name-prefix

Targeted Resource Patching

The globals.patches feature allows you to apply targeted modifications to specific Kubernetes resources in your Helm chart. This provides fine-grained control over resource configuration without modifying the underlying Kustomize files.

Implementation Status: ✅ Fully supported with advanced targeting capabilities including regex patterns, namespace filtering, label/annotation selectors, and wildcard behavior. See table below for specific limitations.

Target Filtering

Each patch contains a target block that lets you filter which objects the patch will affect by combining any of these fields:

| Field | Matches by … | Accepts | |-------|-------------|---------| | group | API group | e.g. apps, batch, empty string "" for core/v1 | | version | API version | v1, v1beta1, etc. | | kind | Resource kind | Deployment, ConfigMap, etc.Regex allowed: .*Set$ | | name | Object name | Exact name or Go-regex: ^web-.* | | namespace | Namespace | prod, staging, etc. | | labelSelector* | Kubernetes label selector | app=web,tier=frontend (simple selectors) | | annotationSelector | Annotation selector | Not yet implemented (planned) |

Important:

  • All supplied conditions are AND-ed together
  • Anything you leave out acts like a wildcard ("match all")
  • (*) Complex label selectors with in operators (e.g., tier in (frontend,backend)) have parsing limitations

Patch Operations

Each patch supports standard JSON Patch operations:

  • add - Add new values or append to arrays
  • remove - Remove properties or array elements
  • replace - Replace existing values

Examples

Basic Resource Targeting

globals:
  patches:
  - target:
      group: apps
      version: v1
      kind: Deployment
      name: web-app
    ops:
    - op: add
      path: /spec/template/spec/containers/0/env/-
      value:
        name: LOG_LEVEL
        value: debug

Regex Pattern Matching

globals:
  patches:
  - target:
      group: apps
      version: v1
      kind: ".*Set$"  # Matches DaemonSet, ReplicaSet, etc.
      name: "^web-.*" # Matches names starting with "web-"
    ops:
    - op: add
      path: /metadata/labels/matched-by-regex
      value: "true"

Label Selector Targeting

globals:
  patches:
  - target:
      labelSelector: "app=web,tier=frontend"
    ops:
    - op: add
      path: /metadata/labels/web-tier
      value: "true"

Annotation Selector Targeting

globals:
  patches:
  - target:
      annotationSelector: "service.beta.kubernetes.io/aws-load-balancer-type,environment=production"
    ops:
    - op: add
      path: /metadata/labels/aws-production-lb
      value: "true"

Combined Targeting (AND Logic)

globals:
  patches:
  - target:
      group: apps
      version: v1
      kind: Deployment
      name: "^web-.*"
      namespace: production
      labelSelector: "app=web,tier=frontend"
      annotationSelector: "deploy.version=2.0"
    ops:
    - op: add
      path: /metadata/labels/fully-matched
      value: "true"

Wildcard Targeting

globals:
  patches:
  - target:
      labelSelector: "type=application"  # Only labelSelector specified
      # group, version, kind omitted = matches ALL resource types
    ops:
    - op: add
      path: /metadata/labels/wildcard-matched
      value: "true"

Advanced Path Operations

globals:
  patches:
  - target:
      kind: Deployment
      name: my-app
    ops:
    # Add environment variable
    - op: add
      path: /spec/template/spec/containers/0/env/-
      value:
        name: NEW_VAR
        value: new_value
    # Remove a label
    - op: remove
      path: /metadata/labels/old-label
    # Replace replicas
    - op: replace
      path: /spec/replicas
      value: 5

Using with Helm Commands

You can also set patches via Helm command line using --set-json:

helm upgrade --install myapp ./chart \
  --set-json 'globals.patches=[{
    "target": {
      "kind": "Deployment",
      "name": "web-app"
    },
    "ops": [{
      "op": "add",
      "path": "/metadata/labels/env",
      "value": "production"
    }]
  }]'

Or for multiple patches:

helm upgrade --install myapp ./chart \
  --set-json 'globals.patches=[
    {
      "target": {
        "group": "apps",
        "version": "v1", 
        "kind": "Deployment",
        "labelSelector": "app=web"
      },
      "ops": [{
        "op": "add",
        "path": "/spec/template/spec/containers/0/env/-",
        "value": {
          "name": "LOG_LEVEL",
          "value": "debug"
        }
      }]
    },
    {
      "target": {
        "kind": "Service"
      },
      "ops": [{
        "op": "add",
        "path": "/metadata/labels/environment",
        "value": "production"
      }]
    }
  ]'

Advanced Helm Targeting Examples

# Regex pattern targeting
helm upgrade --install myapp ./chart \
  --set-json 'globals.patches=[{
    "target": {
      "kind": ".*Set$",
      "name": "^web-.*"
    },
    "ops": [{"op": "add", "path": "/metadata/labels/matched-by-regex", "value": "true"}]
  }]'

# Namespace-specific targeting
helm upgrade --install myapp ./chart \
  --set-json 'globals.patches=[{
    "target": {
      "namespace": "production",
      "kind": "Deployment"
    },
    "ops": [{"op": "add", "path": "/metadata/labels/env", "value": "prod"}]
  }]'

# Label selector targeting (simple selectors)
helm upgrade --install myapp ./chart \
  --set-json 'globals.patches=[{
    "target": {
      "labelSelector": "app=web,tier=frontend"
    },
    "ops": [{"op": "add", "path": "/metadata/labels/web-tier", "value": "true"}]
  }]'

Kustomize replacements with helm values

Kustomize Replacements are used to copy fields from one source into any number of specified targets. Combined with .env file you can use it to dynamically set values in your helm chart using helm values.

During build process when the parametrize list is provided example --parametrize devEnv=overlays/dev/.env --parametrize baseEnv=base/.env the following happens:

  • The parametrize list is a list of pairs, the left side is the key of the value in the helm values, the right side is the path to the file to be read, devEnv=overlays/dev/.env will set the value of devEnv in the helm values to the value of the .env file in the overlays/dev folder.
  • Each of the files in the parametrize list is being read and the values are being randomly set during the kustomize build process.
  • The core logic wraps the results of all overlays into a single helm chart.
  • The random values are replaced with the property accessor based on the left side of the pair {{ .Values.devEnv.propertyName }} with a default value of the actual value from the .env file.

Example: Your kustomization.yaml file contains the following:

configMapGenerator:
- name: example-configmap
  files:
  - .env
replacements:
  - source:
      fieldPath: data.EXAMPLE_PROPERTY
      kind: ConfigMap
      name: example-configmap
    targets:
      - fieldPaths:
        - metadata.namespace
        options:
          create: true
        reject:
        - kind: Namespace
        select: {}

Your .env file contains the following:

EXAMPLE_PROPERTY=example_value

Building the kustomize folder with the following command:

npx helmify-kustomize build ./kustomize-folder --chart-name example-service --target ./helm-chart --parametrize devEnv=overlays/dev/.env

After the build process the following helm template is created:

kind: ConfigMap
apiVersion: v1
metadata:
  name: example-configmap
data:
  EXAMPLE_PROPERTY: {{ .Values.devEnv.EXAMPLE_PROPERTY }}

This is the relevant part of the Values.yaml file that is created:

devEnv:
  EXAMPLE_PROPERTY: example_value

ConfigMap Parametrization

The --parametrize-configmap flag allows you to make specific ConfigMaps in your Helm chart fully parametrizable through Helm values. This transforms static ConfigMap data into dynamic template expressions that are resolved at deployment runtime.

How It Works

When you use --parametrize-configmap <key>=<name>, the tool:

  1. Identifies the ConfigMap by the specified <name> in your Kustomize output
  2. Replaces all static data values in that ConfigMap with Helm template expressions
  3. Creates template expressions that reference .Values.<key> for each data field
  4. Requires you to provide the actual values in your Helm values.yaml or at deployment time

Usage

npx helmify-kustomize build ./kustomize-folder \
  --chart-name example-service \
  --target ./helm-chart \
  --parametrize-configmap appConfig=app-config \
  --parametrize-configmap dbConfig=database-config

Example

Input: Your Kustomize generates a ConfigMap like:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  APP_NAME: my-application
  APP_VERSION: 1.0.0
  DEBUG_MODE: false

Command:

npx helmify-kustomize build ./kustomize-folder \
  --chart-name example-service \
  --target ./helm-chart \
  --parametrize-configmap appConfig=app-config

Output: Generated Helm template:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  APP_NAME: my-app-example
  APP_VERSION: 2.0.0
  DEBUG_MODE: true

Required: You must provide the values in your values.yaml:

appConfig:
  APP_NAME: my-application
  APP_VERSION: 1.0.0
  DEBUG_MODE: false

Important: Any key/value pair you add to the appConfig object will automatically become a key/value pair in the ConfigMap data. This means you can dynamically add new configuration keys without modifying the Helm template.

Deployment-Time Configuration

The real power comes at deployment time when you can override these values:

helm upgrade --install my-app ./helm-chart \
  --set appConfig.APP_NAME="production-app" \
  --set appConfig.DEBUG_MODE="true"

Or using a custom values file:

helm upgrade --install my-app ./helm-chart -f custom-values.yaml

Where custom-values.yaml contains:

appConfig:
  APP_NAME: production-app
  APP_VERSION: 2.0.0
  DEBUG_MODE: true
  # New keys automatically added to ConfigMap
  DATABASE_URL: postgres://prod-db:5432/myapp
  REDIS_URL: redis://prod-redis:6379
  FEATURE_FLAG_X: enabled

Dynamic ConfigMap Example

Here's what happens when you add new properties and change existing ones:

Original ConfigMap (from Kustomize):

apiVersion: v1
kind: ConfigMap
metadata:
  name: database-config
data:
  DB_HOST: localhost
  DB_PORT: "5432"
  DB_NAME: myapp

After parametrization with additional values:

# values.yaml
dbConfig:
  DB_HOST: localhost          # original value
  DB_PORT: "5432"            # original value  
  DB_NAME: production-db     # changed value
  DB_SSL_MODE: require       # new value
  DB_POOL_SIZE: "20"         # new value
  BACKUP_ENABLED: "true"     # new value

Resulting deployed ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: database-config
data:
  DB_HOST: localhost
  DB_PORT: "5432"
  DB_NAME: production-db     # ← changed
  DB_SSL_MODE: require       # ← new
  DB_POOL_SIZE: "20"         # ← new  
  BACKUP_ENABLED: "true"     # ← new

disableNameSuffixHash

By default, Kustomize adds a hash suffix to ConfigMap names to trigger pod restarts when the ConfigMap content changes. When using --parametrize-configmap, you should disable this behavior to maintain consistent ConfigMap names that can be reliably referenced by the parametrization.

Add disableNameSuffixHash: true to your ConfigMap generator in kustomization.yaml:

configMapGenerator:
- name: app-config
  files:
  - app.properties
  options:
    disableNameSuffixHash: true

This ensures that the ConfigMap name remains app-config instead of app-config-abc123hash, allowing the tool to correctly identify and parametrize the ConfigMap by its predictable name.

Best Practices

  1. Use descriptive keys for the parametrization (e.g., appConfig, dbConfig) to make values.yaml clear
  2. Always set disableNameSuffixHash: true for ConfigMaps you want to parametrize
  3. Provide complete values in your values.yaml since the ConfigMap data becomes fully dependent on Helm values
  4. Take advantage of dynamic properties - You can add new configuration keys at deployment time without modifying the Helm template
  5. Use environment-specific values files to maintain different configurations for different environments while using the same template

Dynamic Anchor Resolution

By default, helmify-kustomize automatically enables Dynamic Anchor Resolution to solve a fundamental timing issue between YAML anchors and Helm value overrides. This feature can be disabled with the --disable-dynamic-anchor-replacement flag.

The Problem: YAML Anchors vs Helm Overrides

YAML anchors are a powerful DRY (Don't Repeat Yourself) feature, but they have a critical limitation when used with Helm charts: anchors are resolved during YAML parsing, before Helm can apply any value overrides. This creates a timing issue where Helm's --set commands cannot effectively update anchor values and their references.

Example of the Problem

Consider this values.yaml with anchors:

# values.yaml
app_name: &app_name "my-app"
app_port: &app_port 8080

services:
  frontend:
    name: *app_name
    port: *app_port
  backend:
    name: *app_name
    port: *app_port

Without Dynamic Anchor Resolution:

# This WILL NOT work as expected
helm install myapp ./chart --set app_port=9090

# Result: services.frontend.port and services.backend.port remain 8080
# Because the anchor was already resolved to 8080 during YAML parsing

The --set app_port=9090 only updates the anchor definition, but all the references (*app_port) were already resolved to 8080 during YAML parsing, before Helm could apply the override.

The Solution: Dynamic Anchor Resolution

Dynamic Anchor Resolution solves this by using Helm templates to defer anchor resolution until after Helm processes all value overrides. This allows a single --set command to update both the anchor value and all its references throughout the chart.

With Dynamic Anchor Resolution (enabled by default):

# This WORKS as expected
helm install myapp ./chart --set app_port=9090

# Result: services.frontend.port and services.backend.port are now 9090
# The anchor resolution happens AFTER Helm applies the override

How It Works

The feature works by transforming your values.yaml during the build process:

  1. Anchor Detection: Identifies all YAML anchors (&anchor_name) and their references (*anchor_name)
  2. Template Generation: Creates a Helm template that reconstructs the YAML with dynamic placeholders for anchor values
  3. Runtime Resolution: When you deploy the chart, the template resolves anchor values using the final Helm values (after all overrides are applied)
  4. Natural Reference Resolution: YAML references (*anchor_name) are left untouched, allowing the YAML parser to resolve them naturally after the anchor values are set

Real-World Example

Original values.yaml with anchors:

# Database configuration with anchors
db_host: &db_host "localhost"
db_port: &db_port 5432
db_name: &db_name "myapp"

# Microservices using the same database
services:
  user_service:
    database:
      host: *db_host
      port: *db_port
      name: *db_name

  order_service:
    database:
      host: *db_host
      port: *db_port
      name: *db_name

  inventory_service:
    database:
      host: *db_host
      port: *db_port
      name: *db_name

# Connection strings also using anchors
connection_strings:
  primary: "postgresql://*db_host:*db_port/*db_name"
  readonly: "postgresql://*db_host:*db_port/*db_name?readonly=true"

Advanced: Merge References with Default Values

Dynamic anchor resolution also supports YAML merge references (<<) combined with default values, enabling powerful parent-child chart configurations where child charts can inherit and selectively override parent settings.

Example: Parent-Child Chart with Merge References

Parent Chart values.yaml:

# Define base configuration as an anchor
baseConfig: &baseConfig
  namespace: production
  defaultNamespace: app-default
  labels:
    app: myapp
    tier: backend
  annotations:
    managed-by: helm
    version: "1.0"

# Parent globals use the base config
globals:
  <<: *baseConfig
  namespace: parent-namespace  # Override specific value

# Child chart configuration with dynamic anchors
dynamicAnchors:
  childChartGlobals: &childDefaults
    defaultNamespace: child-default
    labels:
      environment: staging

Child Chart Integration:

# The child chart receives merged configuration
# Parent baseConfig + childChartGlobals overrides
globals:
  <<: [*baseConfig, *childDefaults]
  # Results in:
  # namespace: production (from baseConfig)
  # defaultNamespace: child-default (from childDefaults, overrides baseConfig)
  # labels:
  #   app: myapp (from baseConfig)
  #   tier: backend (from baseConfig)
  #   environment: staging (from childDefaults)
  # annotations: (from baseConfig, unchanged)

Helm Deployment with Overrides:

# Deploy with dynamic overrides
helm install myapp ./chart \
  --set baseConfig.namespace=custom-ns \
  --set dynamicAnchors.childChartGlobals.labels.environment=production

# All references to baseConfig and childDefaults are updated dynamically

Complex Merge Example with Multiple Inheritance

Multi-tier configuration with merge references:

# Base defaults for all environments
defaults: &defaults
  replicas: 1
  resources:
    limits:
      memory: "512Mi"
      cpu: "500m"
    requests:
      memory: "256Mi"
      cpu: "250m"

# Production overrides
prodDefaults: &prodDefaults
  <<: *defaults
  replicas: 3
  resources:
    limits:
      memory: "2Gi"
      cpu: "2000m"
    requests:
      memory: "1Gi"
      cpu: "1000m"

# Staging overrides
stagingDefaults: &stagingDefaults
  <<: *defaults
  replicas: 2
  resources:
    limits:
      memory: "1Gi"
      cpu: "1000m"

# Service configuration using environment-specific defaults
services:
  api:
    <<: *prodDefaults  # Inherits all production settings
    port: 8080

  worker:
    <<: *stagingDefaults  # Inherits staging settings
    port: 8081

# Dynamic anchor for child charts
dynamicAnchors:
  childServiceDefaults:
    <<: *defaults  # Child charts inherit base defaults
    namespace: child-namespace

Deployment with selective overrides:

# Update base defaults - affects all services inheriting from it
helm install myapp ./chart --set defaults.replicas=5

# Update production defaults - affects only services using prodDefaults
helm install myapp ./chart --set prodDefaults.resources.limits.memory=4Gi

# Combine multiple overrides
helm install myapp ./chart \
  --set defaults.replicas=2 \
  --set prodDefaults.replicas=6 \
  --set "dynamicAnchors.childServiceDefaults.namespace=custom-child-ns"

Benefits of Merge References with Dynamic Anchors

  1. Configuration Inheritance: Build complex configuration hierarchies with base settings and environment-specific overrides
  2. DRY Principle: Define common settings once and reuse them across multiple services
  3. Selective Overrides: Override specific values while inheriting the rest
  4. Runtime Flexibility: Change any part of the inheritance chain at deployment time
  5. Parent-Child Chart Compatibility: Pass configuration from parent to child charts seamlessly

Integration with Namespace Resolution

The dynamic anchor feature works seamlessly with the namespace resolution system. Here's an example combining both features:

# Base configuration with namespace settings
baseGlobals: &baseGlobals
  defaultNamespace: app-default
  namePrefix: app-
  labels:
    managed-by: helmify-kustomize

# Environment-specific overrides
prodGlobals: &prodGlobals
  <<: *baseGlobals
  namespace: production  # Override namespace for production
  namePrefix: prod-
  labels:
    environment: production

stagingGlobals: &stagingGlobals
  <<: *baseGlobals
  defaultNamespace: staging-default  # Different default for staging
  namePrefix: stage-
  labels:
    environment: staging

# Apply to globals based on overlay
globals:
  <<: *prodGlobals  # Use production settings by default

# Dynamic anchors for child charts
dynamicAnchors:
  childChartGlobals:
    <<: *stagingGlobals  # Child chart uses staging settings
    namespace: ""  # Clear namespace to use Release.Namespace

Deployment scenarios:

# Scenario 1: Use production namespace from anchor
helm install myapp ./chart
# Result: namespace="production" (from prodGlobals anchor)

# Scenario 2: Override with staging globals at runtime
helm install myapp ./chart --set-json 'globals={"$ref":"#/stagingGlobals"}'
# Result: namespace uses staging-default or Release.Namespace

# Scenario 3: Override specific namespace while keeping other anchor values
helm install myapp ./chart \
  --set prodGlobals.namespace=custom-prod \
  --set prodGlobals.labels.team=platform
# Result: All references to prodGlobals updated with new values

Deployment with overrides:

# Deploy to production with different database settings
helm install myapp ./chart \
  --set db_host=prod-db.example.com \
  --set db_port=5433 \
  --set db_name=myapp_prod

# Result: ALL references are updated:
# - All three services get the production database settings
# - Connection strings are updated with production values
# - Everything stays in sync automatically

Benefits

  1. Single Point of Truth: Update a value once, and all references update automatically
  2. Helm Override Compatibility: Full support for --set and -f values.yaml overrides
  3. Type Preservation: Supports all YAML types (strings, numbers, booleans, objects, arrays)
  4. Clean Values Files: Maintain readable values.yaml with meaningful anchors

Disabling Dynamic Anchor Resolution

If you need to disable this feature (for example, for compatibility testing or if you prefer static anchor resolution), use:

helmify-kustomize build ./kustomize-folder \
  --chart-name my-app \
  --target ./helm-chart \
  --disable-dynamic-anchor-replacement

When disabled, YAML anchors will be resolved during the build process, and Helm overrides will not affect anchor references.

Technical Details

The feature works by:

  1. Generating a _values.yaml.tpl template in your Helm chart
  2. Creating safe getter functions for each anchor with default values
  3. Using Helm's printf function with %v placeholders for type-safe value substitution
  4. Preserving the original YAML structure while making anchor values dynamic

This approach ensures that the final YAML is valid and that all anchor references resolve correctly at runtime.

Contributing

Contributions are welcome! Please submit a pull request or open an issue to discuss improvements or bugs.

License

This project is licensed under the MIT License.

Examples

Example 1: Single Overlay Filter

helmify-kustomize \
  --directory ./my-kustomize \
  --target-folder ./my-helm-chart \
  --chart-name my-app \
  --chart-version 1.0.0 \
  --overlay-filter staging

Example 2: Multiple Overlay Filter

helmify-kustomize \
  --directory ./my-kustomize \
  --target-folder ./my-helm-chart \
  --chart-name my-app \
  --chart-version 1.0.0 \
  --overlay-filter staging,prod

Example 3: With Parameterization

helmify-kustomize \
  --directory ./my-kustomize \
  --target-folder ./my-helm-chart \
  --chart-name my-app \
  --chart-version 1.0.0 \
  --overlay-filter dev \
  --parametrize devEnv=overlays/dev/.env

Example 4: With ConfigMap Parametrization

helmify-kustomize \
  --directory ./my-kustomize \
  --target-folder ./my-helm-chart \
  --chart-name my-app \
  --chart-version 1.0.0 \
  --parametrize-configmap appConfig=app-config-cm \
  --parametrize-configmap dbConfig=database-config-cm

Practical Example

Given a Kustomize directory structure:

my-kustomize/
├── base/
│   ├── kustomization.yaml
│   ├── deployment.yaml
│   └── service.yaml
└── overlays/
    ├── dev/
    │   ├── kustomization.yaml
    │   └── .env
    ├── staging/
    │   ├── kustomization.yaml
    │   └── .env
    ├── prod/
    │   ├── kustomization.yaml
    │   └── .env
    └── test/
        ├── kustomization.yaml
        └── .env

Process only staging and prod overlays:

helmify-kustomize \
  --directory ./my-kustomize \
  --target-folder ./my-helm-chart \
  --chart-name my-app \
  --chart-version 1.0.0 \
  --overlay-filter staging,prod

Process only the dev overlay with kustomize files:

helmify-kustomize \
  --directory ./my-kustomize \
  --target-folder ./my-helm-chart \
  --chart-name my-app \
  --chart-version 1.0.0 \
  --overlay-filter dev \
  --include-kustomize-files

Process all overlays with ConfigMap parametrization:

helmify-kustomize \
  --directory ./my-kustomize \
  --target-folder ./my-helm-chart \
  --chart-name my-app \
  --chart-version 1.0.0 \
  --parametrize-configmap appConfig=app-config-cm
  # No --overlay-filter specified, processes all overlays