DEV Community

Cover image for Helm vs Kustomize: Comparison, Use Cases & How to Combine Them
Kirshi Yin
Kirshi Yin

Posted on • Originally published at Medium

Helm vs Kustomize: Comparison, Use Cases & How to Combine Them

Learn the key differences between Helm and Kustomize and how to combine them for flexible Kubernetes deployments

Kustomize vs Helm: Do You Really Have to Choose?

When teams start working with Kubernetes, two tools often come up: Helm and Kustomize. Both help manage your YAML files. Both aim to simplify Kubernetes deployments. Because of this overlap, many people think they do the same thing.

Some even believe you must pick one — kustomize vs helm. Fortunately, you don’t have to do that. In fact, these tools complement each other and can give you more flexibility and control.

In this article, I’ll help you understand what each tool is good at and how they work together. You’ll also see a step-by-step example of how to configure the Prometheus Helm chart using Kustomize.

Let’s get started!

Understanding Each Tool’s Purpose

Before we jump into the practical example, let’s briefly review what each tool does.

What Is Helm?

Helm acts as a package manager for Kubernetes. It bundles applications into charts. These charts include everything needed to run an application: deployments, services, ingresses, config maps, and more. Helm handles versioning, dependencies, and complex installations.

For instance, when you install a database like PostgreSQL using Helm, you’re getting a well-tested configuration that thousands of other developers have used. The chart maintainers have thought about security, performance, and operational concerns, so you don’t have to start from scratch.

Helm uses Go templates to generate Kubernetes manifests. Below is a typical Helm chart structure:

# Chart.yaml - Chart metadata for an app called my-web-app
apiVersion: v2
name: my-web-app
version: 1.2.0
description: A simple web application
dependencies:
  - name: redis
    version: 10.5.7
    repository: https://charts.bitnami.com/bitnami

# values.yaml - Default configuration values
image:
  repository: nginx
  tag: "1.21"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false
  className: ""
  annotations: {}
  hosts:
    - host: chart-example.local
      paths:
        - path: /
          pathType: Prefix

resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 250m
    memory: 256Mi

autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 100

# templates/deployment.yaml - Template file
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "my-web-app.fullname" . }}
  labels:
    {{- include "my-web-app.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "my-web-app.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "my-web-app.selectorLabels" . | nindent 8 }}
    spec:
      containers:
      - name: {{ .Chart.Name }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        ports:
        - name: http
          containerPort: 80
          protocol: TCP
        resources:
          {{- toYaml .Values.resources | nindent 10 }}
Enter fullscreen mode Exit fullscreen mode

When you run helm install my-app ./my-web-app, Helm processes these templates and generates the final Kubernetes manifests. The templating engine replaces all the {{ .Values.* }} placeholders with actual values from your values.yaml file.

What Is Kustomize?

Kustomize works with plain YAML. It doesn’t use templates. It lets you patch, override, or combine base configurations in different ways. This makes debugging easier because there’s no intermediate templating step.

The main idea is that you:

  • Define a base configuration.

  • Apply overlays for environment-specific changes like dev, staging, or prod.

Let’s say you have a base Kubernetes Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-web-app
  labels:
    app: my-web-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-web-app
  template:
    metadata:
      labels:
        app: my-web-app
    spec:
      containers:
      - name: web
        image: nginx:1.21
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
Enter fullscreen mode Exit fullscreen mode

In the base folder, you also need a kustomization.yaml:

resources:
  - deployment.yaml
Enter fullscreen mode Exit fullscreen mode

Kustomize needs the kustomization.yaml to know what files belong to the base setup.

Now let’s say you want to increase the resource limits in the development environment. You can create a patch with the specific settings:

# overlays/development/deployment-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-web-app
spec:
  template:
    spec:
      containers:
      - name: web
        resources:
          requests:
            cpu: 500m
            memory: 512Mi
          limits:
            cpu: 1000m
            memory: 1Gi
Enter fullscreen mode Exit fullscreen mode

And reference this patch in the dev overlay config:

# overlays/development/kustomization.yaml - dev kustomization
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base # the base config's location

patches: # the patch to apply
  - path: development-patch.yaml
    target:
      kind: Deployment # this will be applied to the deployment.yaml manifest
      name: my-web-app
Enter fullscreen mode Exit fullscreen mode

You can preview the final patched manifest:

kustomize build overlays/development
Enter fullscreen mode Exit fullscreen mode

You can also apply it to the cluster using kubectl:

kubectl apply -k overlays/development
Enter fullscreen mode Exit fullscreen mode

When to Combine Kustomize and Helm

Now let’s explore some common scenarios where combining these tools makes sense.

Scenario 1: Multi-Environment Application Deployment

Imagine you’re deploying a microservices application across development, staging, and production environments. Each environment requires unique resource limits, ingress rules, secrets, and scaling policies.

Using Helm alone would require multiple values files that become hard to maintain. You’d end up with complex conditional logic in your templates. Using Kustomize alone means you can’t easily share your application configuration with other teams.

The Combined Solution:

  • Package your application as a Helm chart for easy distribution.

  • Use Kustomize overlays to handle environment-specific customizations.

Scenario 2: Third-Party Application Customization

Let’s say you need to deploy Prometheus for monitoring. The official Prometheus Helm chart has hundreds of configuration options. However, you need some specific customizations that aren’t directly supported by the chart’s values.

The Combined Solution:

  • Use the official Prometheus Helm chart as your base.

  • Apply Kustomize patches for your specific requirements.

Scenario 3: Compliance and Security Overlays

In regulated industries, you often need to apply consistent security policies across all applications. These might include security contexts, network policies, or resource quotas that must be applied uniformly.

The Combined Solution:

  • Package applications normally with Helm.

  • Use Kustomize to apply organization-wide security policies.

How Both Tools Work Together

Let’s walk through exactly what happens when you combine these tools, because understanding the process helps you see the benefits.

Helm and Kustomize combined for kubernetes deployments

Step 1: Helm Processing: When you run helm template, Helm takes your chart templates and values, processes them through the Go template engine, and outputs standard Kubernetes YAML manifests. These manifests are complete and ready to apply to your cluster.

Step 2: Kustomize Processing: Kustomize takes these Helm-generated manifests as base resources. It applies your overlays and patches, creating the final customized manifests. Kustomize doesn’t care that these base manifests came from Helm — it just sees standard Kubernetes YAML.

Step 3: Deployment: The final manifests are applied to your Kubernetes cluster.

Optional: Use Kustomize with Helm’s Post-Renderer

When you apply Kustomize outside of Helm, using something like:

helm template myapp ./mychart > rendered.yaml
kustomize build overlays/dev | kubectl apply -f -
Enter fullscreen mode Exit fullscreen mode

You lose Helm features like helm upgrade, release tracking, and rollback.

If you want to keep using Helm to install and upgrade your app, but still patch the rendered manifests with Kustomize, you can use the --post-renderer flag (available since Helm 3.1+).

This lets Helm do all the templating and release management. But before applying the manifests to the cluster, Helm passes them to Kustomize. Kustomize applies your patches, and then Helm proceeds with the deployment.

For example, the command would look like this:

helm upgrade --install myapp ./mychart --post-renderer ./kustomize-wrapper.sh
Enter fullscreen mode Exit fullscreen mode

You can read more about this advanced Helm feature in the official docs.

Hands-on Example: Customize a Prometheus Helm Chart with Kustomize

Feeling ready to try Helm and Kustomize together?

In this demo, you’ll install Prometheus with Helm, then use Kustomize to:

  • Add a custom label to all resources.

  • Patch the prometheus-server service to change its type to LoadBalancer.

Create this folder structure:

mkdir -p helm-kustomize-demo/base
mkdir -p helm-kustomize-demo/overlays/dev
cd helm-kustomize-demo
Enter fullscreen mode Exit fullscreen mode

Generate Prometheus base using Helm:

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

helm template prometheus prometheus-community/prometheus \
  --values prometheus-values.yaml > base/prometheus.yaml
Enter fullscreen mode Exit fullscreen mode

Create an empty prometheus-values.yaml because we won’t need it in this demo.

Explanation:

  • helm template: Renders the chart templates to raw YAML (instead of installing them directly).

  • prometheus: The name of the Helm release (can be anything).

  • prometheus-community/prometheus: The name of the chart from the Helm repo.

  • --values prometheus-values.yaml: Custom values file to override chart defaults.

  • base/prometheus.yaml: Output the raw Kubernetes manifests to a file (no deployment yet).

So you’re extracting the Helm chart into raw YAML and saving it in your Kustomize base directory.

Create the base/prometheus/kustomization.yamlfile:

resources:
  - prometheus.yaml
Enter fullscreen mode Exit fullscreen mode

Add this Kustomize patch to add a label called dev to overlays/dev/label-patch.yaml :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus-server
  labels:
    env: dev
Enter fullscreen mode Exit fullscreen mode

Create this patch to change the service type in overlays/dev/service-patch.yaml :

apiVersion: v1
kind: Service
metadata:
  name: prometheus-server
spec:
  type: LoadBalancer
Enter fullscreen mode Exit fullscreen mode

This patch changes the service type from ClusterIP to LoadBalancer. Here’s a snippet from the Prometheus Helm chart’s service template:

# Source: prometheus/charts/alertmanager/templates/services.yaml
apiVersion: v1
kind: Service
metadata:
  name: prometheus-alertmanager
  labels:
    helm.sh/chart: alertmanager-1.23.0
    app.kubernetes.io/name: alertmanager
    app.kubernetes.io/instance: prometheus
    app.kubernetes.io/version: "v0.28.1"
    app.kubernetes.io/managed-by: Helm
  namespace: default
spec:
  type: ClusterIP
# code omitted for brevity....
Enter fullscreen mode Exit fullscreen mode

Create the Kustomize file for the dev enviroment — overlays/dev/kustomization.yaml :

resources:
  - ../../base/prometheus

patches:
  - path: label-patch.yaml
    target:
      kind: Deployment
      name: prometheus-server
  - path: service-patch.yaml
    target:
      kind: Service
      name: prometheus-server
Enter fullscreen mode Exit fullscreen mode

You should end up with the following folder tree:

helm-kustomize-demo/
├─ base/
│   ├─ prometheus.yaml
│   └─ kustomization.yaml
└─ overlays/
    └─ dev/
        ├─ kustomization.yaml
        ├─ label-patch.yaml
        └─ service-patch.yaml
Enter fullscreen mode Exit fullscreen mode

Deploy it to the Kubernetes cluster:

kustomize build overlays/dev | kubectl apply -f -
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • kustomize build : Reads the base YAML and applies patches defined in the custom overlay.

  • | kubectl apply -f -: Sends the result directly to kubectl for deployment.

Test the configuration:

  • Run kubectl get svc to check if prometheus-server is of type LoadBalancer.

  • Run kubectl get deploy prometheus-server -o yaml | grep env to check for the custom label.

Conclusion

In this article, you saw the differences between Helm and Kustomize and how they can work together. You can select the best option for your needs based on your specific use case.

You also saw a code example of how to combine Kustomize and Helm to deploy Prometheus.

Let’s do a quick recap of each tool’s benefits:

Helm Benefits:

  • Package distribution and sharing across teams.

  • Semantic versioning for application releases.

  • Dependency resolution between related services.

  • Complex application bundling with hooks and tests.

  • Large ecosystem of community-maintained charts.

Kustomize Benefits:

  • Template-free configuration that’s easier to debug.

  • Environment-specific customization without duplication.

  • Patch-based modifications that are more maintainable (you can see all changes in version control).

  • Native kubectl integration (Since Kubernetes 1.14!).

  • Better support for GitOps workflows (because of its declarative configuration management directly with Kubernetes manifests).

I hope this article has been useful, and now you have a clear vision of both tools.

Want to go deeper with Helm? I wrote a practical ebook for beginners, available as a downloadable PDF on Gumroad. It teaches you the fundamentals with practical examples and short lessons. You can also visit my Helm course for beginners.

Want to go deeper with Helm? I wrote a practical ebook for beginners, available as a downloadable PDF on Gumroad. It teaches you the fundamentals with practical examples and short lessons. You can also visit my Helm course for beginners.

You can find the example code for this article in my GitHub repo.

Thank you for reading, and see you next time!

This story was originally published on https://medium.com/curious-devs-corner/helm-vs-kustomize-comparison-use-cases-how-to-combine-them-ffeae155c3df

Top comments (0)