Chart fidelity
The examples use Universal Helm Chart application 0.4.3, published on Artifact Hub. The image names and hostnames are examples; replace them before deploying.
We rendered six application shapes to compare a shared platform chart with charts owned by individual application teams. The results show where each model becomes expensive to operate.
The examples use Universal Helm Chart application 0.4.3, published on Artifact Hub. The image names and hostnames are examples; replace them before deploying.
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.
application 0.4.30.3.5gateway.networking.k8s.io/v1 via chart route.*The focus is template ownership: a shared chart versus one chart per application. CI image publishing, ApplicationSet generators, and repository promotion need a separate GitOps design. For a shorter introduction to the shared-chart model, read Why one Helm chart is enough.
Shared chart / platform API: one application chart owns Deployment, Service, optional Ingress/HTTPRoute, Job/CronJob, HPA, and ServiceMonitor. App teams ship values (and rare escape hatches). GitOps wrappers pin one chart version.
Chart-per-application: each service owns its templates/. Policy changes become N template MRs. Autonomy is high; drift and operating cost scale with service count.
The right choice depends on how similar the workloads are, how often policy changes, and how much independence an application team needs.
The evidence pack renders six shapes through the same 0.4.3 package:
envSecrets and HTTP probesASPNETCORE_* environment variablesenvSecretsreadOnlyRootFilesystem: false exceptionEach language profile under examples/one-chart-vs-many/ renders a Deployment, Service, and HTTPRoute. The shared examples set runAsNonRoot: true and related keys in values. The chart package leaves securityContext empty by default, so the platform baseline must supply it. Our small dedicated chart omits the block entirely. That is an easy mistake to repeat when teams copy a minimal template and then maintain it independently.
From package inventory of application 0.4.3:
templates/ (~1405 LOC excluding NOTES.txt)values.yaml + JSON schemaA minimal dedicated FastAPI chart in examples/one-chart-vs-many/dedicated-contrast/ ships three templates (Deployment, Service, HTTPRoute) with no securityContext block. Scaling that pattern to six languages means six template trees to patch for every probe, label, or security change.
The battle test used one Universal Helm Chart dependency per wrapper across twelve Argo CD Applications: six apps in dev and prod. All twelve reported Synced/Healthy on the reference cluster. That earlier run used chart 0.3.5.
# Abbreviated FastAPI values
replicaCount: 2
image: ghcr.io/example-org/fastapi
imageTag: "1.0.0"
imagePullPolicy: IfNotPresent
service:
port: 8080
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
route:
enabled: true
gateway: external
gatewayNamespace: kgateway-system
sectionName: https-wildcard
hostname: battle-fastapi-prod.example.comFull shape files also set probes, resources, and envSecrets: examples/one-chart-vs-many/shapes/*/values.yaml.
# environments/prod/demo-api/Chart.yaml
dependencies:
- name: application
version: "0.4.3"
repository: https://chaser100.github.io/u-helm-chart
# environments/prod/demo-api/values.yaml
application:
image: ghcr.io/example-org/fastapi
imageTag: "1.0.0"
route:
enabled: true
gateway: external
gatewayNamespace: kgateway-system
sectionName: https-wildcard
hostname: battle-fastapi-prod.example.comCI updates imageTag, while the platform team reviews chart version bumps. Application teams control what they deploy; the platform team controls the shared Kubernetes contract.
application chart version with template/policy changes.Accepted=True / ResolvedRefs=True.Rollback: revert the wrapper Chart.yaml dependency to the previous chart version, sync Argo CD, and confirm that Kubernetes restores the prior ReplicaSet. Track the versions used by every rollout ring so a partial upgrade does not become a permanent mixed fleet.
With one chart per application, the same policy change requires a merge request for every template tree. The longer rollout gives those charts more time to diverge.
The chart has first-class keys for conventional applications. Vendor-specific objects such as a kgateway TrafficPolicy should not masquerade as portable route.* fields. Version 0.4.3 has no route.cors or route.rateLimit key. Add the policy through extraManifests and label it clearly:
extraManifests:
- |
apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
name: demo-api-cors
labels:
networking.contract/kind: kgateway-specific
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: demo-api
cors:
allowOrigins:
- https://app.example.comIf most of the release lives in escape hatches, use a dedicated chart. A shared chart should remain an application contract, not grow into a second Kubernetes configuration language.
Multi-workload applications can also make a values file unwieldy. In an assessment made against chart 0.3.7, one application needed roughly 423 lines of application-specific values compared with about 630 lines of Universal values. Much of the extra text repeated security contexts and selectors rather than business configuration. At that point, improve the shared API or move the workload to a dedicated chart.
envSecrets or use an External Secrets controller.Prefer the shared chart when the shape is Deployment/Service/(Ingress|HTTPRoute) plus optional Job/CronJob/HPA/ServiceMonitor and exceptions are rare and labeled.
Prefer a dedicated chart when any of these hold:
extraManifests / extraDeployments become a second unreadable chartWhy one Helm chart is enough → · Gateway API examples → · Browse language examples →