DocumentsDocuments
Kustomize Patterns for Taming Vendor Helm Charts

July 17, 2026

Two Tools, One Problem

Most platform teams end up using both Kustomize and Helm — Helm for vendor-distributed charts, Kustomize for everything else. The question isn’t which tool to use. It’s how to combine them when the vendor’s chart doesn’t do what you need.

This post covers four patterns for integrating Helm charts with Kustomize overlays in ArgoCD, when to use each, and the one technical distinction that determines which pattern will actually work: whether you need to add fields or remove them.

The examples come from two repos:

Pattern 1: Helm Primary, Kustomize Overlay (Multi-Source)

ArgoCD supports multi-source Applications — one source for the Helm chart, another for Kustomize patches:

apiVersion: argoproj.io/v1alpha1
kind: Application
spec:
  sources:
    - repoURL: https://vendor.example.com/charts
      chart: their-app
      targetRevision: 2.1.0
      helm:
        valuesFiles:
          - $patches/values-override.yaml
    - repoURL: https://github.com/my-org/platform-configs
      path: overlays/their-app
      ref: patches

Helm renders the chart first, then Kustomize applies strategic merge patches on top. The vendor chart is primary — you’re patching its output.

Use when: You need to add fields (extra labels, annotations, tolerations, resource limits) or modify values that the chart’s values.yaml doesn’t expose. Strategic merge works well here because you’re adding, not removing.

Pattern 2: Kustomize Primary, Helm as Input (Single Source)

Kustomize can render Helm charts directly via helmCharts in the kustomization:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

helmCharts:
  - name: their-app
    repo: https://vendor.example.com/charts
    version: 2.1.0
    releaseName: their-app
    namespace: vendor-ns
    valuesFile: values.yaml

patches:
  - target:
      kind: Deployment
      name: their-app
    patch: |-
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: their-app
      spec:
        template:
          spec:
            securityContext:
              runAsNonRoot: true

One ArgoCD Application, one source. Kustomize is the orchestrator — it pulls in the Helm chart as raw material and patches the output.

Use when: You want the simplest possible setup. Everything is one Application, sync status is clean. Good for single-chart vendors.

Pattern 3: App-of-Apps with Layered Overrides

Some vendors ship a parent Helm chart that creates ArgoCD Application CRs (app-of-apps pattern), each deploying a child chart. You need to override the children without breaking the parent’s orchestration.

The approach: two ArgoCD Applications coordinated by sync waves.

  • Wave 0: Installs the vendor’s chart with minimal values. Uses ignoreDifferences on the fields that wave 1 will own.
  • Wave 1: Applies overrides via SSA (ServerSideApply=true, Force=true) with retry/backoff for race conditions.

The two apps perform an “SSA handshake” — wave 0 explicitly ignores the fields that wave 1 manages, preventing sync loops where each app fights the other.

Use when: The vendor’s app-of-apps topology matters to you (separate Applications per component, independent sync/rollback) and you only need to add or update fields, not remove them.

Don’t use when: You need to remove fields. SSA can’t do that — see the next section.

Pattern 4: Kustomize with JSON Patch (RFC 6902)

Same as Pattern 2, but using JSON Patch instead of strategic merge:

patches:
  - target:
      kind: Deployment
      name: their-app
    patch: |-
      - op: replace
        path: /spec/template/spec/securityContext
        value:
          runAsNonRoot: true
          seccompProfile:
            type: RuntimeDefault
      - op: replace
        path: /spec/template/spec/containers/0/securityContext
        value:
          allowPrivilegeEscalation: false
          capabilities:
            drop: [ALL]

The replace operation overwrites the entire object at that path. The vendor’s runAsUser: 0 doesn’t get merged alongside your fix — it’s gone entirely.

Use when: The vendor’s chart sets fields that need to be deleted, not overridden. On OpenShift, this is almost always securityContext — vendor charts that set runAsUser: 0 or capabilities.add: [NET_BIND_SERVICE] can’t be fixed by adding fields alongside them. SCC checks the full context object.

The Decision Matrix

The core question: do you need to add fields or remove them?

Need Patch type Patterns that work
Add fields (labels, resources, tolerations) Strategic merge 1, 2, 3
Update values (replica count, image tag) Strategic merge or Helm values 1, 2, 3
Remove fields (runAsUser: 0, capabilities) JSON Patch replace 4 (or Kyverno)
Replace entire objects (securityContext) JSON Patch replace 4 (or Kyverno)

If you’re on OpenShift and the vendor’s chart runs as root, you almost certainly need Pattern 4 or Kyverno. Strategic merge will add runAsNonRoot: true alongside runAsUser: 0, and SCC will still reject the pod.

OpenShift-Specific Overrides

Beyond security contexts, there are a few OpenShift-specific resources that vendor charts don’t account for:

Routes instead of Ingress: Vendor charts create Kubernetes Ingress objects. On OpenShift, you typically want Routes with TLS edge termination. A strategic merge patch can add Routes, but you may also need to delete or ignore the vendor’s Ingress objects.

Resource quotas: Vendor charts often ship without resource requests/limits. OpenShift namespaces with quotas will reject pods that don’t declare resources. JSON Patch’s add operation handles this cleanly.

NetworkPolicy: If the vendor doesn’t ship NetworkPolicies, you can add them via Kustomize overlay. If they ship overly permissive ones, you need JSON Patch to replace them.

Further Reading

← Back to Blog