🎉 DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

Helmfile vs Kustomize for Multi-Environment Kubernetes Deployments

Both Helmfile and Kustomize manage environment-specific Kubernetes configurations, but they take completely different approaches. Here's the practical comparison with real config examples.

DevOpsBoysMay 17, 20264 min read
Share:Tweet

You need to deploy the same app to dev, staging, and prod — but with different replicas, resource limits, and config values in each environment. You have two popular tools: Helmfile and Kustomize. Here's how they differ.


The Core Difference

Kustomize — Patch-based. Start with a base configuration, apply overlays (patches) for each environment. Pure YAML, built into kubectl.

Helmfile — Declarative Helm. A single file defines which Helm charts deploy to which environments with which values. Orchestrates multiple Helm releases.


Kustomize — Base + Overlays

Kustomize directory structure:

k8s/
├── base/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── kustomization.yaml
└── overlays/
    ├── dev/
    │   ├── kustomization.yaml
    │   └── patch-replicas.yaml
    ├── staging/
    │   ├── kustomization.yaml
    │   └── patch-resources.yaml
    └── production/
        ├── kustomization.yaml
        └── patch-production.yaml

Base deployment:

yaml
# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-api
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: my-api
          image: my-api:latest
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "200m"
              memory: "256Mi"
yaml
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml

Production overlay:

yaml
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
  - ../../base
namespace: production
namePrefix: prod-
commonLabels:
  environment: production
patches:
  - patch-production.yaml
images:
  - name: my-api
    newTag: v1.5.2   # Pin specific version
yaml
# overlays/production/patch-production.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-api
spec:
  replicas: 5          # Override replicas
  template:
    spec:
      containers:
        - name: my-api
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"
            limits:
              cpu: "2"
              memory: "2Gi"
bash
# Apply production
kubectl apply -k overlays/production/
 
# Preview what will be applied
kubectl kustomize overlays/production/
 
# Dry run
kubectl apply -k overlays/production/ --dry-run=client

Kustomize strengths:

  • Built into kubectl (no extra tool)
  • Native ArgoCD support
  • Pure YAML — no templating language to learn
  • Easy to see exactly what changes per environment

Kustomize weaknesses:

  • Complex transformations require strategic merge patches
  • Managing many environments gets repetitive
  • Not great for deploying external Helm charts with your overlays
  • ConfigMap/Secret generation is limited

Helmfile — Declarative Helm Orchestration

Helmfile manages multiple Helm releases in a single declarative file.

yaml
# helmfile.yaml
repositories:
  - name: bitnami
    url: https://charts.bitnami.com/bitnami
  - name: my-charts
    url: https://charts.mycompany.com
 
environments:
  dev:
    values:
      - environments/dev/values.yaml
  staging:
    values:
      - environments/staging/values.yaml
  production:
    values:
      - environments/production/values.yaml
 
releases:
  - name: my-api
    namespace: "{{ .Environment.Name }}"
    chart: my-charts/my-api
    version: 1.5.2
    values:
      - values/my-api-common.yaml
      - values/my-api-{{ .Environment.Name }}.yaml
 
  - name: redis
    namespace: "{{ .Environment.Name }}"
    chart: bitnami/redis
    version: 19.0.0
    values:
      - values/redis-{{ .Environment.Name }}.yaml
    condition: redis.enabled   # Only deploy if redis.enabled=true
 
  - name: postgresql
    namespace: "{{ .Environment.Name }}"
    chart: bitnami/postgresql
    version: 14.0.0
    installed: "{{ ne .Environment.Name "dev" }}"  # Skip in dev

Environment-specific values:

yaml
# environments/production/values.yaml
redis:
  enabled: true
 
# values/my-api-production.yaml
replicaCount: 5
resources:
  requests:
    cpu: 500m
    memory: 512Mi
  limits:
    cpu: "2"
    memory: 2Gi
ingress:
  enabled: true
  host: api.mycompany.com
autoscaling:
  enabled: true
  minReplicas: 5
  maxReplicas: 20
bash
# Install helmfile
brew install helmfile
 
# Sync all releases to production
helmfile -e production sync
 
# Preview changes
helmfile -e production diff
 
# Deploy specific release only
helmfile -e production --selector name=my-api sync
 
# Destroy everything (careful!)
helmfile -e production destroy

Helmfile strengths:

  • Manages multiple Helm releases as one unit
  • Conditional deployments (skip redis in dev, include in prod)
  • Works with any Helm chart (your own + external)
  • Templating with Go templates for complex logic
  • Supports hooks (pre/post sync scripts)

Helmfile weaknesses:

  • Extra tool to install and maintain
  • Not natively supported by ArgoCD (needs the helmfile plugin)
  • Go template syntax can be confusing
  • Harder to read than pure YAML

Side-by-Side Comparison

KustomizeHelmfile
Templating❌ Patches only✅ Go templates
kubectl built-in❌ Separate tool
ArgoCD native⚠️ Plugin needed
Multi-chart orchestration✅ Core feature
External Helm charts⚠️ Difficult✅ Native
Learning curveLowMedium
YAML verbosityMediumLow
Conditional deployments⚠️ Workarounds✅ Native

When to Use Each

Use Kustomize when:

  • You're managing your own Kubernetes manifests (not Helm charts)
  • You want ArgoCD GitOps without extra plugins
  • Environments differ only in replicas, images, resource sizes
  • You want the simplest possible solution
  • Team is new to Kubernetes — lower learning curve

Use Helmfile when:

  • You're deploying multiple external Helm charts (postgres, redis, ingress) plus your own
  • Environments have structural differences (dev has no redis, prod does)
  • You need conditional deployments or complex environment logic
  • You want to orchestrate an entire application stack in one file

The most common pattern in 2026:

  • Kustomize for your own app manifests
  • Helmfile or Helm + ArgoCD for the full application stack including third-party charts

Kustomize + Helm Together

You can use Kustomize to patch Helm chart output:

yaml
# kustomization.yaml
helmCharts:
  - name: my-api
    repo: https://charts.mycompany.com
    version: 1.5.2
    releaseName: my-api
    valuesFile: values.yaml
patches:
  - target:
      kind: Deployment
      name: my-api
    patch: |-
      - op: add
        path: /spec/template/spec/containers/0/env/-
        value:
          name: EXTRA_ENV_VAR
          value: "production-specific"

For Helm, Kustomize, and GitOps hands-on labs, KodeKloud has courses covering multi-environment deployment patterns with real Kubernetes clusters.

Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments