Helm vs Kustomize
Overview
Section titled “Overview”Use Helm when you have things that are identical except for one or two config changes, and you want many copies. Other than that, treat it like a package manager.
People use Helm to create a template for Kubernetes resources. If they have configured Kubernetes to work with e.g. mkcert, superset etc., use their chart and call it a day.
If you have things that have a lot of overlap (e.g. same front/back/db split e.g. Rach and Stone & Chalk), then use an override on a base, hoist duplication up where possible.
If there’s limited overlap (e.g. Uni Remit and National Intermodal), use separate charts/bases entirely — never force shared structure as it adds complexity. See Helm Charts in Kustomize for details on the rough edges of mixing the two.
Deciding
Section titled “Deciding”graph TD
A["New Kubernetes resource"] --> B{"Who wrote it?"}
B -->|"Someone else<br />(open source, vendor)"| C["✅ Helm<br />install their chart<br />override with values.yaml"]
B -->|"You wrote it"| D{"Will other teams install it?"}
D -->|"Yes"| E["✅ Helm<br />package as a chart<br />publish to a repo"]
D -->|"No, only you deploy it"| F{"What kind of resource?"}
F -->|"App workload<br />(deployment, service, ingress)"| G{"Are your apps structurally identical?"}
F -->|"Cluster policy<br />(RBAC, NetworkPolicy,<br />namespaces, quotas)"| H["✅ Kustomize<br />plain YAML, auditable<br />overlays per cluster"]
G -->|"Yes, same app<br />different config"| I["✅ Helm<br />one chart, many values files"]
G -->|"Mostly similar<br />some structural differences"| J["✅ Kustomize<br />base + per-site overlays"]
G -->|"Completely different"| K["Separate charts<br />or separate bases"]
graph LR
subgraph "Kustomize (you own it, plain YAML)"
direction TB
K1["infrastructure/base/"]
K1 --> K2["namespaces"]
K1 --> K3["RBAC roles + bindings"]
K1 --> K4["NetworkPolicies"]
K1 --> K5["ResourceQuotas"]
K6["infrastructure/overlays/"]
K6 --> K7["doks/<br />24 namespaces"]
K6 --> K8["k0s/<br />12 namespaces"]
end
subgraph "Helm (someone else wrote it)"
direction TB
H1["Third-party charts"]
H1 --> H2["ingress-nginx"]
H1 --> H3["cert-manager"]
H1 --> H4["postgres-operator"]
end
subgraph "Helm (you wrote it, same app × 12)"
direction TB
H5["charts/website/"]
H5 --> H6["templates/<br />deployment, service,<br />ingress, serviceaccount"]
H5 --> H7["values/<br />website-a.yaml<br />website-b.yaml<br />...website-l.yaml"]
end
K7 -->|"kubectl apply -k"| CLUSTER["☸ DOKS Cluster"]
K8 -->|"kubectl apply -k"| LOCAL["☸ k0s Cluster"]
H1 -->|"helm install"| CLUSTER
H5 -->|"helm install × 12"| CLUSTER
H5 -->|"helm install × 12"| LOCAL
graph TD
A["12 websites to deploy"] --> B{"How similar are they?"}
B -->|"Identical app<br />just different config<br />(domain, DB, env vars)"| C["One Helm chart<br />12 values files"]
C --> C1["charts/website/"]
C --> C2["values/website-a.yaml<br />values/website-b.yaml<br />..."]
C --> C3["helm install × 12"]
B -->|"Mostly the same<br />but some have extras<br />(sidecars, cron jobs)"| D["Kustomize<br />base + overlays"]
D --> D1["apps/base/<br />common deployment"]
D --> D2["apps/overlays/website-a/<br />apps/overlays/website-c/<br />(patches for differences)"]
D --> D3["kubectl apply -k × 12"]
B -->|"Different apps<br />that happen to be websites"| E["Separate charts<br />or separate bases"]
E --> E1["Don't force<br />shared structure"]