How many Helm releases live in your cluster if Argo CD rolls out the applications? Zero. helm ls is empty, helm history has nothing to show. Argo CD uses Helm as a template engine: it runs helm template, takes the rendered manifests and applies them with its own engine — helm install is never called. The project FAQ says so plainly, and the code confirms it (util/helm/helm.go).
That has an immediate consequence for the "Helm versus Kustomize" argument: the points about release history, helm rollback and lifecycle management through hooks do not apply inside a GitOps loop. History and rollback come from Argo CD — argocd app history, argocd app rollback.
Three things are left to compare: the language you describe configuration in, where the artifact comes from, and whether the tool can pull that artifact out of a closed registry. The first one is a matter of taste. The other two became hard technical limits in 2026.
What changed over the year
The Helm 3 line is closing. Release 3.22.0 (September 2026) is its last minor, and a deliberately limited one: it only refreshes the Kubernetes client libraries for newer clusters. Security patches run until 10 February 2027. HIP-0012 originally set November 2026; the maintainers added three months. After February, 3.x gets nothing at all. Current Helm is 4.3.0, from the same September window.
Something else matters more for GitOps. Starting with Argo CD 3.5, charts are rendered by Helm 4 only. The spec.source.helm.version field is ignored — you may leave it in place, it has no effect. Version 3.5.3 ships Helm 4.2.1; the still-supported 3.3 and 3.4 branches run Helm 3.19.4. The template engine version in your pipeline is set by the Argo CD version, not by whatever an engineer installed locally.
Which has a practical consequence for CI. If the pipeline runs helm template or helm diff with a Helm 3 binary while the cluster-side Argo CD is already on 3.5, you are comparing the output of two different renderers and hoping they match. Pin the Helm version in CI to the Argo CD version rather than to whatever sits in the runner image. Topology decides the rest: with hub-and-spoke a single Argo CD on the hub does the rendering, so there is one Helm version in the loop; with Argo CD in every cluster the clusters drift apart across Argo CD minors, and with them across template engine versions.
Kustomize moves more quietly: 5.8.2 landed on 30 September 2026, the previous minors being 5.8.1 in February and 5.8.0 in November 2025. Three releases in eleven months. The version built into kubectl trails the standalone binary: kubectl 1.36 and 1.37 carry 5.8.1, and Argo CD 3.5.3 carries 5.8.1 as well.
The upgrade that breaks an internal registry
Argo CD's move to Helm 4 brought a non-obvious breaking change — OCI registries without TLS. Helm 4 wants an explicit --plain-http where its predecessor managed without one. In Argo CD you set a flag in the repository Secret:
stringData:
insecureOCIForceHttp: "true"
The subtlety sits in dependencies. If Chart.yaml points at repository: oci://… on a registry without TLS, that registry has to be registered in Argo CD separately, as a repository with the same flag. Under Helm 3 such dependencies were fetched transparently, with no registration. There is a ready-made trap too: set both --insecure-skip-server-verification and --insecure-oci-force-http, and Helm 4 silently drops --plain-http.
Where they coexist and where the combination breaks
Mixing the engines is almost unavoidable: someone else's chart plus your own values. Two approaches work.
The first is a multi-source Application. The chart comes from a Helm or OCI registry, the values live in Git and are wired in through ref and $values. Credentials stay in Argo CD's repository Secrets, which is where they belong.
spec:
sources:
- repoURL: https://prometheus-community.github.io/helm-charts
chart: prometheus
targetRevision: 15.7.1
helm:
valueFiles:
- $values/charts/prometheus/values.yaml
- repoURL: https://git.example.com/org/value-files.git
targetRevision: dev
ref: values
The second is Kustomize with the helmCharts field: the chart is inflated inside Kustomize and patches go on top. That is how people install Argo Rollouts and half the CRD operators. This approach has a ceiling, and it is documented.
The --enable-helm flag that helmCharts needs can only be turned on globally in Argo CD — through kustomize.buildOptions: --enable-helm in argocd-cm, for every Kustomize application at once. There is no per-app option for it; the alternative is writing a CMP plugin.
Authentication is absent entirely. The generator assembles a helm pull command without a single credential flag: no --username, no --password, no --registry-config, no --plain-http. Argo CD's repository Secrets never reach this path; they apply to the native Helm source only. The generator does support the oci:// scheme — there is a dedicated branch for it in pullCommand() — but a private registry, or one without TLS, cannot be configured declaratively at all. On an internal Harbor without TLS, the "Kustomize + helmCharts + Argo CD 3.5" combination simply will not build.
The Kustomize maintainers make no secret of it: the generator is meant as a limited subset of Helm, accepting bug fixes, critical security work and flags analogous to helm template. Support for private registries and authentication is not planned — the next iteration is supposed to become a KRM function. Read that document as a statement of intent: it also promises no OCI support, and the oci:// scheme is already in the generator's code.
Hooks and determinism
Argo CD recognises helm.sh/hook and maps hooks onto its own phases, but the mapping is incomplete, and the differences change behaviour:
pre-installandpre-upgradecollapse intoPreSync. Argo CD does not distinguish a first install from an update — everything is a sync. A database migration Job annotated"helm.sh/hook": pre-upgrade,pre-installwill run on every sync. If it is not idempotent, your data will tell you.pre-rollback,post-rollback,test-success,test-failureandhook-delete-timeoutare ignored.- A single
argocd.argoproj.io/hookof your own in the application disables all Helm hooks.
The second thread is render determinism. helm template re-runs on every comparison against the cluster. A chart that generates a password with randAlphaNum produces a permanent OutOfSync: the value changes on every render and the diff never converges. The fix is to pin the value in values. Plain Kustomize, without helmCharts, has no such class of problem by construction: kustomize build output is deterministic. Determinism is the real strength here; the absence of templates guarantees nothing by itself — wrap a chart in helmCharts and you render the same template with the same randAlphaNum and the same permanent OutOfSync.
Kustomize charges its own price for determinism: the patch lives apart from the object, and seeing the final manifest means building the overlay. Add the tech debt accumulated in older trees. Version 5.0.0 deprecated patchesStrategicMerge, patchesJson6902 and vars, while bases went back in 2.1.0; the replacements are patches, replacements and resources, converted by kustomize edit fix. They will not be removed from kustomize.config.k8s.io/v1beta1, but they will not make it into the future v1 API, so the fixing has to happen. Auto-converting vars to replacements can rewrite a lot of files and change the build output — do it in a clean git tree.
Whatever renders the manifests, what you validate is the render output, not the source. Policies at the output of helm template and kustomize build catch what has no business reaching the cluster, regardless of which engine you picked.
When to pick what
| Situation | Choice | Why |
|---|---|---|
| Third-party chart from a public or private registry | Native Helm source plus values via multi-source | Credentials, TLS and OCI flags live in repository Secrets |
| Your own manifests, many environments | Kustomize: bases and overlays | Deterministic output, patches instead of branching templates |
| Chart needs edits on top: annotations, limits, a sidecar | Kustomize plus helmCharts | A patch over the render, but the registry must be public and on TLS |
| Internal registry without TLS or with authorisation | Native Helm source only | The helmCharts generator passes neither credentials nor --plain-http |
| Chart goes to an external consumer | Helm | Chart versioning, dependencies, OCI distribution |
What follows from this
The choice is dictated by where the artifact comes from. Where a chart has to be pulled from a registry that demands credentials or runs without TLS, one path remains workable — the native Helm source in Argo CD: it alone has a channel to the repository Secrets. Where the manifests are yours and live in the same Git, Kustomize gives you a deterministic build and patches instead of branching templates. A taste for Go templates plays no part in this decision; what plays a part is who the registry will open the door for.