Why Self-Host Kubernetes Package Management?

Managing Kubernetes manifests at scale is one of the hardest problems in self-hosted infrastructure. As your cluster grows from a handful of deployments to dozens of services, raw YAML files become unmanageable. You need a way to templatize configurations, manage environment-specific overrides, version your releases, and roll back when things go wrong.

Without a package management layer, you face several problems:

  • Configuration drift — manual kubectl apply edits get lost and diverge from your Git repository
  • No versioning — there’s no concept of a “release” you can roll back to
  • Duplication — the same Deployment YAML is copy-pasted across dev, staging, and production with minor tweaks
  • No dependency management — installing a multi-service application requires running 15+ kubectl apply commands in the right order

Package management tools solve all of this. They give you templating, versioning, dependency resolution, and one-command deploy/rollback for complex Kubernetes applications. The three dominant open-source approaches in 2026 are Helm, Kustomize, and Helmfile, each with a fundamentally different philosophy.


The Contenders: Helm vs Kustomize vs Helmfile

Three tools dominate the Kubernetes package management landscape, each representing a different design philosophy.

Helm (29,761 Stars)

Helm is the original Kubernetes package manager, often called the “apt/yum of Kubernetes.” It uses Go templates to parameterize YAML manifests and packages them into Charts — versioned, distributable bundles of Kubernetes resources. Charts can declare dependencies on other charts, making it possible to install entire application stacks with a single helm install command.

Helm’s chart ecosystem is massive — the Artifact Hub hosts thousands of community-maintained charts for popular self-hosted software. If you want to deploy PostgreSQL, Redis, Prometheus, or any other infrastructure component, there’s almost certainly a Helm chart ready to go.

Best for: Teams that want a rich ecosystem of pre-built charts, dependency management, and the ability to package and distribute their own applications as reusable bundles.

Kustomize (12,037 Stars)

Kustomize takes the opposite approach: no templating, just YAML overlays. Instead of writing Go templates with {{ .Values.replicaCount }}, you write plain Kubernetes YAML and layer customizations on top using a kustomization.yaml file. Kustomize then merges the base configuration with your overlays at apply time.

Since Kubernetes 1.14, Kustomize is built directly into kubectl via kubectl apply -k, meaning zero additional tooling is required. There’s no chart repository, no release history stored in the cluster, and no templating language to learn — just standard Kubernetes YAML with a composition layer.

Best for: Teams that prefer plain YAML over templating, want built-in kubectl support, and need simple environment-specific overlays without the complexity of a full package manager.

Helmfile (5,076 Stars)

Helmfile sits above Helm, managing multiple Helm releases declaratively. While Helm handles individual chart installations, Helmfile lets you define an entire fleet of releases in a single YAML file — including which chart version, which values override, and which namespace each release targets. It can install, upgrade, and sync dozens of Helm releases in the correct dependency order.

Helmfile also integrates with Kustomize — you can point a Helmfile release at a Kustomize directory and it will render the manifests through Helm’s release management. This makes it a powerful orchestration layer that combines both tools.

Best for: Teams already using Helm for individual charts but needing a declarative way to manage 20+ releases across multiple namespaces and environments.


Feature Comparison

FeatureHelmKustomizeHelmfile
ApproachGo templatesYAML overlaysDeclarative Helm orchestration
Learning curveModerateLowModerate
Built into kubectlNoYes (kubectl apply -k)No
Chart ecosystemMassive (Artifact Hub)N/A (uses raw YAML)Uses Helm charts
Dependency managementYes (Chart.yaml)Manual orderingYes (needs/helmfile.yaml)
Release historyStored in cluster (Secrets/ConfigMaps)No release conceptDelegates to Helm
Rollbackhelm rollbackManual (re-apply previous YAML)Delegates to Helm
Environment overrides--values / --setOverlays (base + dev/prod)Environments in helmfile.yaml
TemplatingGo templates (Sprig functions)None (plain YAML + patches)Go templates for helmfile.yaml
GitOps friendlyYes (with ArgoCD/Flux)Yes (native in ArgoCD/Flux)Yes (via helmfile apply)
LanguageGoGoGo
GitHub stars29,76112,0375,076
Last updated2026-04-262026-04-282026-04-28

Deep Dive: Helm

Architecture

Helm has two main components:

  • helm CLI — the client tool you use to install, upgrade, and manage charts
  • Tiller — the server-side component (removed in Helm v3; Helm v3 is client-only)

Helm v3 removed the Tiller component entirely, eliminating the cluster-side RBAC complexity that made Helm v2 difficult to manage. All release state is now stored as Kubernetes Secrets or ConfigMaps in the namespace where the release is installed.

Installing Helm

1
2
3
4
5
6
# Install Helm via the official script
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# Verify installation
helm version
# version.BuildInfo{Version:"v3.17.0", GitCommit:"...", GoVersion:"go1.24.1"}

Creating and Deploying a Chart

1
2
3
4
5
6
# Create a new chart
helm create myapp
# Creates: myapp/Chart.yaml, myapp/values.yaml, myapp/templates/

# Inspect the default values
cat myapp/values.yaml

A minimal values.yaml for a self-hosted web application:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
replicaCount: 2

image:
  repository: ghcr.io/myorg/myapp
  tag: "1.2.0"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: true
  className: nginx
  hosts:
    - host: myapp.example.com
      paths:
        - path: /
          pathType: Prefix

resources:
  limits:
    cpu: 500m
    memory: 256Mi
  requests:
    cpu: 100m
    memory: 128Mi

persistence:
  enabled: true
  size: 10Gi
  accessMode: ReadWriteOnce

Deploy with environment-specific overrides:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Production deployment
helm install myapp ./myapp \
  --namespace production \
  --create-namespace \
  --set replicaCount=3 \
  --set image.tag="1.2.0" \
  --values production-values.yaml

# Upgrade
helm upgrade myapp ./myapp \
  --namespace production \
  --set image.tag="1.3.0"

# Rollback to previous release
helm rollback myapp 1 --namespace production

# View release history
helm history myapp --namespace production

Using Community Charts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Add a chart repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

# Install PostgreSQL from the Bitnami chart
helm install postgresql bitnami/postgresql \
  --namespace database \
  --create-namespace \
  --set auth.postgresPassword="secret" \
  --set primary.persistence.size=20Gi

# Install Prometheus stack
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install monitoring prometheus-community/kube-prometheus-stack \
  --namespace monitoring --create-namespace

Self-Hosting a Chart Repository with Harbor

For teams that need to share charts internally, Harbor provides a self-hosted OCI-compatible chart registry. You can push and pull Helm charts just like container images. See our Harbor container registry guide for a complete Harbor setup with Helm chart support.


Deep Dive: Kustomize

Architecture

Kustomize works by reading a kustomization.yaml file that references base resources and applies transformations:

  • Resources — the base YAML files to start from
  • Patches — targeted modifications (Strategic Merge Patch or JSON Patch)
  • ConfigMapGenerator/SecretGenerator — generate ConfigMaps and Secrets from files or literals
  • Transformers — commonPrefix, commonLabels, namespace, images, replicas

The output is pure Kubernetes YAML — no templating artifacts, no hidden state.

Project Structure

A typical Kustomize project layout:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
k8s/
├── base/
│   ├── kustomization.yaml
│   ├── deployment.yaml
│   ├── service.yaml
│   └── ingress.yaml
├── overlays/
│   ├── dev/
│   │   ├── kustomization.yaml
│   │   └── replica-patch.yaml
│   └── prod/
│       ├── kustomization.yaml
│       └── resources-patch.yaml

Base Configuration

base/kustomization.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml
  - ingress.yaml

commonLabels:
  app: myapp
  managed-by: kustomize

commonAnnotations:
  environment: shared

base/deployment.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: ghcr.io/myorg/myapp:latest
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 256Mi

Environment Overlays

overlays/dev/kustomization.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

namePrefix: dev-

patches:
  - target:
      kind: Deployment
      name: myapp
    patch: |
      - op: replace
        path: /spec/replicas
        value: 1

overlays/prod/kustomization.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

namePrefix: prod-

patches:
  - target:
      kind: Deployment
      name: myapp
    patch: |
      - op: replace
        path: /spec/replicas
        value: 3
      - op: replace
        path: /spec/template/spec/containers/0/resources/limits/memory
        value: 512Mi

images:
  - name: ghcr.io/myorg/myapp
    newTag: "1.2.0"

Deploying with Kustomize

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Dry run to preview rendered YAML
kubectl kustomize overlays/dev

# Apply development environment
kubectl apply -k overlays/dev

# Apply production environment
kubectl apply -k overlays/prod

# Verify
kubectl get all -l app=myapp -n production

Since Kustomize is built into kubectl, no separate installation is needed. Just organize your YAML files and run kubectl apply -k.


Deep Dive: Helmfile

Architecture

Helmfile reads a helmfile.yaml that declares multiple Helm releases with their chart sources, values, and deployment order. It then orchestrates helm install / helm upgrade calls in the correct sequence, handling dependencies and parallelization automatically.

Installing Helmfile

1
2
3
4
5
# Install via the official script
curl -fsSL https://raw.githubusercontent.com/helmfile/helmfile/main/get_helmfile.sh | bash

# Verify
helmfile version

Helmfile Configuration

A production helmfile.yaml managing an entire self-hosted infrastructure stack:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
repositories:
  - name: bitnami
    url: https://charts.bitnami.com/bitnami
  - name: prometheus-community
    url: https://prometheus-community.github.io/helm-charts
  - name: ingress-nginx
    url: https://kubernetes.github.io/ingress-nginx

environments:
  dev:
    values:
      - environments/dev.yaml
  prod:
    values:
      - environments/prod.yaml

releases:
  # Infrastructure layer
  - name: ingress-nginx
    namespace: ingress
    chart: ingress-nginx/ingress-nginx
    version: "4.12.0"
    wait: true

  - name: postgresql
    namespace: database
    chart: bitnami/postgresql
    version: "16.2.0"
    values:
      - values/postgresql.yaml
    needs:
      - ingress-nginx

  # Application layer
  - name: myapp
    namespace: production
    chart: ./charts/myapp
    values:
      - values/myapp.yaml
    needs:
      - database/postgresql

Environment-specific values in environments/prod.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
postgresql:
  auth:
    postgresPassword: "${POSTGRES_PASSWORD}"
  primary:
    persistence:
      size: 50Gi

myapp:
  replicaCount: 5
  resources:
    limits:
      memory: 1Gi

Deploying with Helmfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Apply all releases
helmfile apply --environment prod

# Sync (apply + prune removed releases)
helmfile sync --environment prod

# Diff to preview changes
helmfile diff --environment prod

# Selective deploy
helmfile apply --selector app=myapp

# List all releases
helmfile list

Helmfile’s needs field ensures releases are deployed in the correct order — PostgreSQL before your application, ingress controller before anything that needs a load balancer.

Helmfile with Kustomize

Helmfile can render Kustomize directories as Helm releases:

1
2
3
4
5
6
7
releases:
  - name: myapp-kustomize
    namespace: production
    chart: ./k8s/overlays/prod
    installed: true
    # Helmfile detects this is a Kustomize directory
    # and renders it through its release management

When to Use Which Tool

Choose Helm when:

  • You need access to the massive chart ecosystem on Artifact Hub
  • You’re deploying standard software (PostgreSQL, Redis, Prometheus) that already has maintained charts
  • You need dependency management between components
  • You want release history and rollback capability built in
  • You need to package and distribute your own applications as reusable bundles

Choose Kustomize when:

  • Your team prefers plain YAML over templating languages
  • You want minimal tooling — Kustomize is already in kubectl
  • You have relatively simple customization needs (replica counts, image tags, resource limits)
  • You’re using ArgoCD or Flux natively (both support Kustomize out of the box)
  • You want to avoid the complexity of chart maintenance

Choose Helmfile when:

  • You already use Helm for individual charts but manage 10+ releases
  • You need a single declarative file describing your entire infrastructure
  • You want dependency ordering between releases across namespaces
  • You need environment-specific release configurations without duplicating Helm commands
  • You want to combine Helm charts and Kustomize directories under one management layer

For most self-hosted homelabs starting out, Kustomize is the simplest path — no extra tools, just organized YAML. As complexity grows, teams naturally migrate to Helm for the chart ecosystem. And once you’re managing dozens of Helm releases, Helmfile becomes the orchestration layer that keeps everything declarative and reproducible. For production GitOps workflows, see our ArgoCD vs Flux guide for how these tools integrate with continuous deployment pipelines.


Production Best Practices

1. Pin Chart Versions

Never use latest or floating versions for Helm charts in production. Always pin to a specific version:

1
2
3
4
5
# In helmfile.yaml — always pin versions
releases:
  - name: postgresql
    chart: bitnami/postgresql
    version: "16.2.0"  # pinned, not "*"

2. Use Separate Values Files per Environment

Keep environment-specific configuration in separate files rather than inline --set flags:

1
2
3
4
5
# Good: values are tracked in Git
helm install myapp ./chart --values values/prod.yaml

# Bad: values are ephemeral and not tracked
helm install myapp ./chart --set replicaCount=5 --set image.tag="1.2.0"

3. Validate Before Deploying

1
2
3
4
5
6
7
8
# Helm: dry-run with server-side validation
helm install myapp ./chart --dry-run --debug

# Helmfile: diff before apply
helmfile diff --environment prod

# Kustomize: preview rendered output
kubectl kustomize overlays/prod | kubectl diff -f -

4. Store Secrets Separately

Never put secrets in Helm values or Kustomize files. Use a dedicated secrets management solution like the External Secrets Operator to pull secrets from HashiCorp Vault, AWS Secrets Manager, or similar backends.

5. Set Up a Chart Repository

For teams publishing internal charts, set up a self-hosted OCI-compatible registry. Harbor, Zot, or even a simple HTTP server with helm package + helm repo index works. This keeps your internal charts versioned and discoverable alongside community charts.


FAQ

Is Helm or Kustomize better for beginners?

Kustomize is easier for beginners because it requires no additional installation (it’s built into kubectl) and uses plain YAML without templating. You organize your manifests into directories and apply them with kubectl apply -k. Helm requires learning Go template syntax, understanding Chart structure, and managing a separate CLI tool, but offers more powerful features for complex deployments.

Can I use Helm and Kustomize together?

Yes. Helmfile supports rendering Kustomize directories as Helm releases, and you can also run helm template to generate plain YAML and then apply Kustomize overlays on top. Many teams use Helm for third-party charts (PostgreSQL, Prometheus) and Kustomize for their own application manifests.

Does Kustomize support conditionals like Helm templates?

No. Kustomize intentionally does not support conditionals or loops. It works purely through YAML composition — base resources merged with overlay patches. If you need conditional logic (e.g., “if production, add this extra container”), you either create separate overlay directories or use Helm for that specific component.

How do I roll back a failed deployment with each tool?

Helm: helm rollback <release-name> <revision> — Helm stores release history in the cluster. Kustomize: Manually re-apply the previous YAML from Git (git checkout <previous-commit> && kubectl apply -k overlays/prod). Helmfile: Delegates to Helm for rollback — helm rollback on the specific release.

Which tool works best with GitOps (ArgoCD/Flux)?

All three work well, but Kustomize has the most native support. Both ArgoCD and Flux have built-in Kustomize support — you just point them at a kustomization.yaml directory. ArgoCD also has first-class Helm support. Helmfile works with ArgoCD via the ApplicationSet controller or the Helmfile plugin.

How do I manage secrets in Kubernetes deployments?

Never store secrets in Helm values files or Kustomize resources in plain text. Use the External Secrets Operator to sync secrets from external secret stores (Vault, AWS Secrets Manager, Azure Key Vault), or use Sealed Secrets to encrypt secrets so they can safely be stored in Git. For a detailed comparison of these approaches, see our Kubernetes secrets management guide.

Can Helmfile manage releases across multiple Kubernetes clusters?

Yes. Helmfile supports multiple kube contexts through the kubeContext field on each release:

1
2
3
4
5
6
7
releases:
  - name: myapp
    kubeContext: production-cluster
    chart: ./charts/myapp
  - name: myapp
    kubeContext: staging-cluster
    chart: ./charts/myapp

This lets you define your entire multi-cluster infrastructure in a single helmfile.yaml.