Chart fidelity
Helm examples use Universal Helm Chart application 0.4.3 keys from values.yaml and configuration.md. Reproducible sources: examples/helm-vs-kustomize/ in this site repo. Replace ghcr.io/example-org/* images and hostnames.
Deploy the same representative API with Universal Helm Chart values and with Kustomize overlays—then decide where each tool owns the contract, without crowning a false winner.
Helm examples use Universal Helm Chart application 0.4.3 keys from values.yaml and configuration.md. Reproducible sources: examples/helm-vs-kustomize/ in this site repo. Replace ghcr.io/example-org/* images and hostnames.
args / multi-port / service.enabledFrom application 0.4.3: main container args (default []); Deployment revisionHistoryLimit (default 10); multi-port via containerPorts and service.ports; optional chart-managed Service via service.enabled (default true). When service.ports is set, Ingress / simple HTTPRoute / NOTES use the first entry (templates/_helpers.tpl application.servicePort). Legacy single-port service.name / service.port / service.protocol / service.appProtocol remain supported. Set service.enabled: false when the workload does not need a chart-managed Service. Do not invent other port keys.
externalSecretHooksBehavior change in application 0.4.2: ExternalSecret objects in extraManifests are ordinary resources by default. The chart does not add Helm hooks automatically. Recommend explicit lifecycle/ordering annotations on the manifest (for example argocd.argoproj.io/sync-wave: "-5"). Set externalSecretHooks.enabled: true (default false) only when legacy automatic helm.sh/hook: pre-install,pre-upgrade / hook-weight: "-5" / hook-delete-policy: before-hook-creation behavior is required (templates/1_extra-manifests.yaml). Do not invent other externalSecret* keys.
universal/application 0.4.3 (helm pull from https://chaser100.github.io/u-helm-chart)apps/v1 Deployment, v1 Service/ServiceAccount, gateway.networking.k8s.io/v1 HTTPRouteDeployment, Service, ServiceAccount, HTTPRouteMeasured claims cite golden renders under examples/helm-vs-kustomize/. Recommendations are labeled as such. No unverified performance or “hours saved” numbers.
Target workload: one HTTP API (demo-api) with probes, resources, env, Service on port 8080, and an HTTPRoute attached to a platform Gateway. Dev and prod differ by replicas, resources, APP_ENV, and hostname.
Helm path: one chart release + values files. Kustomize path: base manifests + overlays that patch the same fields. Neither path creates the Gateway—that stays platform-owned.
image: ghcr.io/example-org/demo-api
imageTag: "1.2.0"
imagePullPolicy: IfNotPresent
replicaCount: 1
serviceAccount:
create: true
automount: false
service:
port: 8080
env:
- name: APP_ENV
value: dev
route:
enabled: true
gateway: external
gatewayNamespace: kgateway-system
sectionName: https
hostname: demo-api-dev.example.com
path: /
pathMatchType: PathPrefixroute.gatewayNamespace must match the platform Gateway namespace. This series uses lab/platform kgateway-system (aligned with the kgateway articles). Replace if your platform uses another NS—do not invent a chart default.
Hardening: set chart key serviceAccount.automount: false when the workload does not call the Kubernetes API (default chart value is true). Kustomize base mirrors automountServiceAccountToken: false on the ServiceAccount.
Full files: examples/helm-vs-kustomize/helm/values-dev.yaml and values-prod.yaml (replicaCount: 3, larger resources, demo-api.example.com).
Base ships Deployment, Service, ServiceAccount, and HTTPRoute. The dev overlay patches env and hostname; prod also patches resources and sets replicas: 3.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
replicas:
- name: demo-api
count: 1
patches:
- target:
kind: HTTPRoute
name: demo-api
patch: |-
- op: replace
path: /spec/hostnames/0
value: demo-api-dev.example.comhelm repo add universal https://chaser100.github.io/u-helm-chart helm repo update helm template demo-api-dev universal/application --version 0.4.3 \ -f examples/helm-vs-kustomize/helm/values-dev.yaml | rg '^kind:' kustomize build examples/helm-vs-kustomize/kustomize/overlays/dev | rg '^kind:'
Measured: both paths emit the same kind set—Deployment, Service, ServiceAccount, HTTPRoute. Hostnames and replica counts match the encoded env intent. Line counts differ (Helm templates carry chart labels/helpers; Kustomize base is minimal YAML)—that is structure, not a speed claim.
.tgz), Helm repo / OCI, Artifact Hub. Consumers pin --version 0.4.3. Official: Helm Charts.Recommendation: when many services share Deployment/Service/HTTPRoute shape, package once (Helm chart) and vary values. When you already own final YAML and need env deltas, overlays stay lighter.
image/imageTag, replicaCount, service.port, env, probes, resources, route.* / ingress.*.replicas, JSON6902 patches, strategic merge). You maintain the base YAML yourself (or generate it).Complement: offline or CI helm template → commit into a Kustomize base when platform policy must patch labels/sidecars after packaging. Argo CD composition (accurate): a single Application source is either Helm (chart) or Kustomize (path)—Argo CD does not natively chain Helm render then Kustomize as a built-in post-render step on one source. Combine via (a) multi-source Applications (chart + Git values), (b) a Config Management Plugin that you own, or (c) render Helm offline and sync the resulting Kustomize directory. Validate your Argo CD version docs before relying on multi-source or CMP.
Measured: application 0.4.3 ships values.schema.json; helm lint -f values-*.yaml passes on the evidence pack. Schema uses JSON Schema with additionalProperties: true—unknown keys are not rejected by default; still prefer documented keys only.
Kustomize validates kustomization structure at build time; it does not provide an application-level values schema equivalent to Helm’s chart schema. Policy engines (Kyverno, OPA Gatekeeper) apply after render for both paths.
Official: Helm schema files.
Chart.yaml dependencies / subcharts (chart dependencies). Measured for this chart: Universal Helm Chart application 0.4.3 is a leaf chart (type: application, no dependencies entry)—it does not pull Redis/Postgres as subcharts. Wire data stores as separate releases.resources, components, and remote bases. No Helm-style dependency resolver; version pins are Git refs / tags you choose.# Helm: render + diff values layers helm template demo-api universal/application --version 0.4.3 -f values-dev.yaml > /tmp/dev.yaml helm template demo-api universal/application --version 0.4.3 -f values-prod.yaml > /tmp/prod.yaml diff -u /tmp/dev.yaml /tmp/prod.yaml | head # Kustomize: overlay diff diff -u \ <(kustomize build examples/helm-vs-kustomize/kustomize/overlays/dev) \ <(kustomize build examples/helm-vs-kustomize/kustomize/overlays/prod) | head # Cluster-facing (either path) kubectl diff -f <(helm template ...) # or kustomize build helm get values <release> # Helm release state only
Helm keeps release history / values on the cluster when installed via Helm. Pure Kustomize/GitOps often has no release secret—source of truth is Git. Neither fact implies fewer outages; it changes where you look when debugging.
Argo CD syncs one source type per source entry: Helm charts (source.chart + helm.valueFiles / valuesObject) or Kustomize directories (source.path). That is composition of Application sources—not a native Helm→Kustomize post-render pipeline inside one source. Illustrative Applications (marked DO NOT APPLY AS-IS): examples/helm-vs-kustomize/argocd/applications.illustrative.yaml.
repoURL to the Helm repo (https://chaser100.github.io/u-helm-chart), not the chart’s Git remote, when using chart:; pin targetRevision to 0.4.3.repoURL is a public or private Git remote you control (illustrative pack uses https://github.com/example-org/gitops-bridge.git—replace it); pin Git targetRevision.helm template into a Kustomize tree—do not assume Argo CD auto-runs Kustomize after Helm for a single source.Official: Argo CD Helm, Argo CD Kustomize.
Not measured here: live Argo CD sync timings on a cluster. Render parity is verified offline; treat Application YAML as pattern documentation until your GitOps repo adopts it.
Scoped comparison for templating vs overlays (not a full Gateway migration guide):
Gateway in kgateway-system (illustrative: examples/helm-vs-kustomize/kgateway/shared-gateway.illustrative.yaml). Neither Helm values nor app overlays should create it in production.route.* (gateway, gatewayNamespace, hostname, …). Kustomize keeps the HTTPRoute in base and patches hostnames/env per overlay—same ownership split, different config model.TrafficPolicy (CRD), not chart route.*. Helm ships it through extraManifests (snippet: kgateway/values-extraManifests.snippet.yaml). Kustomize adds the same YAML as an overlay resources entry. Label networking.contract/kind: kgateway-specific and review against kgateway docs.Measured limitation: Universal Helm Chart application 0.4.3 has no first-class route.cors / TrafficPolicy keys—escape hatch only. See also kgateway migration patterns and examples/gateway-api-helm-contract/.
With Universal Helm Chart, app teams stay on values; escape hatches (extraManifests, extraDeployments) exist when the model is insufficient—see Why one Helm chart is enough.
0.4.4 does not move pinned consumers automatically. Every Application / CI job that pins targetRevision: 0.4.3 or --version 0.4.3 must bump its own pin. Shared packaging reduces template drift; it does not collapse N pins into one silent upgrade.helm template) when APIs or label conventions change—N bases if teams forked YAML; each overlay consumer still reviews its own patches.kubectl edit is equally wrong in either model.Recommendation: prefer a shared chart package for fleet-wide defaults, but treat every pinned consumer bump as an explicit change; keep Kustomize for environment-specific last-mile patches or non-chart workloads.
route.enabled: true without route.gateway — template required() fails at helm template/install (route.gateway is required when route.enabled=true); JSON Schema alone does not reject the empty default. Example fixture: examples/helm-vs-kustomize/negative/helm-missing-gateway.yaml.Programmed — traffic fails; not a Helm-vs-Kustomize difference.kustomize build fails. Example fixture: examples/helm-vs-kustomize/negative/kustomize-bad-patch/.nameSuffix on HTTPRoute without updating backendRefs — Service name drift (avoided in this pack by stable names).additionalProperties: true; silent no-ops if no template consumes them.helm template to Argo CD hard refresh manifests.serviceAccount.automount: true on workloads that never call the API — unnecessary token exposure; this pack sets false.| Context | Prefer Helm | Prefer Kustomize | Combine |
|---|---|---|---|
| Many services, one Deployment/Service/HTTPRoute shape | Yes — shared application chart + values | Only if you refuse charts | Chart values in GitOps bridge |
Need values JSON Schema / helm lint | Yes (values.schema.json) | No native app schema | Generate base from Helm, overlay patch |
| Last-mile label/sidecar policy on any YAML | Chart hooks limited | Yes — overlays/components | Offline/CI Helm→Kustomize tree, multi-source, or owned CMP—not native single-source post-render |
| Publish reusable package to Artifact Hub | Yes | No chart package | — |
| Single app, unique objects, no reuse | Optional | Yes — plain YAML + overlays | — |
| Subchart / dependency resolver | Helm dependencies (not used by this leaf chart) | Compose resources manually | Separate releases |
Universal Helm Chart is the reusable packaging contract. Kustomize is the YAML patch toolkit. Teams that already standardize on the chart should not rewrite templates as overlays just to “use Kustomize.” Teams with mature overlay libraries should not invent a private chart if a shared chart already encodes the same objects—unless they need escape hatches the values model cannot express.
Recommendation: Helm for the application contract; Kustomize for platform-wide mutations and exceptions; Argo CD to reconcile Helm or Kustomize sources (compose deliberately—do not assume native Helm→Kustomize chaining).
6f3f53e; noindex removed)Related: Why one Helm chart is enough → · Gateway API vs Ingress → · Getting started →
Why one Helm chart is enough → · One chart vs many → · GitOps repository structure → · Gateway API vs Ingress →