Designing a Scalable Helm Platform with Subcharts: A Practical, Production-Ready Approach
Introduction
Helm is often introduced as a simple templating tool for Kubernetes. In practice, however, its real power lies elsewhere:
Helm is a system composition and versioning tool for infrastructure.
In this article, we will walk through a complete, production-grade approach to designing a multi-service platform using Helm subcharts, focusing on:
- Clean architecture and separation of concerns
- Proper value hierarchy (
globalvs service-specific) - Naming strategies to avoid collisions
- Reusable, generic templates
- Packaging and distributing charts via Nexus
The system consists of three independent services:
devops-apiiplocation-apitotp-api
Each service is developed in Go and versioned independently.
https://github.com/faustobranco/devops-db/tree/master/infrastructure/resources
https://github.com/faustobranco/devops-db/tree/master/infrastructure/helms
1. Service Layer: Independent Applications
Each service lives in its own repository:
https://gitlab.devops-db.internal/infrastructure/resources/devops-api
https://gitlab.devops-db.internal/infrastructure/resources/iplocation-api
https://gitlab.devops-db.internal/infrastructure/resources/totp-api
These services expose simple HTTP endpoints (/health, /version, etc.), which makes them ideal for demonstrating platform composition.
2. Helm Charts per Service
Each service has its own Helm chart:
https://gitlab.devops-db.internal/infrastructure/helms/devops-api
https://gitlab.devops-db.internal/infrastructure/helms/iplocation-api
https://gitlab.devops-db.internal/infrastructure/helms/totp-api
2.1 Chart.yaml: Versioning Strategy
Example:
apiVersion: v2
name: devops-api
type: application
version: 1.0.4
appVersion: "1.1.2"
Important Distinction
version→ Helm chart version (controls deployment logic)appVersion→ Application version (informational)
Helm does not deploy appVersion — it deploys what your templates define (e.g., image.tag).
2.2 Template Design Philosophy
Each chart is:
- Generic
- Reusable
- Driven entirely by values
Example: Deployment (simplified)
metadata:
name: {{ printf "%s-%s" .Release.Name .Chart.Name }}
labels:
app: {{ .Chart.Name }}
3. Naming Strategy (Critical)
When using subcharts, all resources share the same:
.Release.Name
Without proper naming, this leads to collisions.
Problem
devops-api → creates Service "devops-platform"
totp-api → creates Service "devops-platform"
💥 Conflict.
Solution
name: {{ printf "%s-%s-%s" .Release.Name .Chart.Name $name }}
Result
devops-platform-devops-api
devops-platform-iplocation-api
devops-platform-totp-api
Rule
metadata.name → must be unique
labels → should represent service identity
4. Values Hierarchy
This is one of the most important aspects of Helm architecture.
4.1 Platform values.yaml
global:
environment: prod
devops-api:
replicaCount: 1
ingress:
enabled: true
host: devops-api.devops-db.internal
image:
repository: nexus.devops-db.internal/devops_images/devops-api
pullPolicy: IfNotPresent
tag: "1.1.2"
service:
port: 80
secrets:
devops-api:
type: Opaque
data:
GITLAB_TOKEN: Z2xwYXQtdWN0WEhTcDN6cW5MQUsxQmJSMXRhRzg2TVFwMU9qWUguMDEuMHcxaXJydWly
POSTGRES_PASSWORD: MTIzNHF3ZXI=
POSTGRES_USER: ZGV2b3BzX2FwaQ==
env:
DEVOPSAPI_VERSION: v1.1.2
configMaps:
db-hosts:
data:
customers_dev: postgresql.devops-db.internal
inventory_dev: postgresql.devops-db.internal
orders_dev: postgresql.devops-db.internal
payment_dev: postgresql.devops-db.internal
reporting_dev: postgresql.devops-db.internal
customers_staging: postgresql.devops-db.internal
inventory_staging: postgresql.devops-db.internal
orders_staging: postgresql.devops-db.internal
payment_staging: postgresql.devops-db.internal
reporting_staging: postgresql.devops-db.internal
customers_prod: postgresql.devops-db.internal
inventory_prod: postgresql.devops-db.internal
orders_prod: postgresql.devops-db.internal
payment_prod: postgresql.devops-db.internal
reporting_prod: postgresql.devops-db.internal
iplocation-api:
replicaCount: 1
ingress:
enabled: true
host: iplocation-api.devops-db.internal
image:
repository: nexus.devops-db.internal/devops_images/iplocation
pullPolicy: IfNotPresent
tag: "1.0.0"
service:
port: 80
env:
IPLOCATOR_VERSION: v1.0.0
totp-api:
replicaCount: 1
ingress:
enabled: true
host: totp-api.devops-db.internal
image:
repository: nexus.devops-db.internal/devops_images/totp-validator
pullPolicy: IfNotPresent
tag: "1.0.0"
service:
port: 80
env:
TOTPVALIDATOR_VERSION: v1.0.0 4.2 How Values Flow
platform values
↓
subchart scope
↓
templates
Key Insight
.Values.devops-api → only visible inside devops-api chart
.Values.global → visible everywhere
5. Dynamic Secrets and ConfigMaps
5.1 Secret Template (Reusable)
{{- if .Values.secrets }}
{{- range $name, $secret := .Values.secrets }}
---
apiVersion: v1
kind: Secret
metadata:
name: {{ printf "%s-%s-%s" $.Release.Name $.Chart.Name $name }}
namespace: {{ $.Release.Namespace }}
type: {{ $secret.type }}
data:
{{- range $key, $value := $secret.data }}
{{ $key }}: {{ $value }}
{{- end }}
{{- end }}
{{- end }}5.2 ConfigMap Template
{{- if .Values.configMaps }}
{{- range $name, $config := .Values.configMaps }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ printf "%s-%s-%s" $.Release.Name $.Chart.Name $name }}
namespace: {{ $.Release.Namespace }}
data:
{{- range $key, $value := $config.data }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- end }}
{{- end }}5.3 Why This Matters
This design allows:
✔ zero hardcoded environment variables
✔ dynamic configuration
✔ reusable charts
✔ consistent structure
6. Packaging and Publishing Charts
6.1 Package Charts
Each service chart must be packaged before being published.
helm package devops-api/.
helm package iplocation-api/.
helm package totp-api/.
This command generates a .tgz archive for each chart:
devops-api-1.0.4.tgz
iplocation-api-1.0.5.tgz
totp-api-1.0.6.tgzWhy this matters
The generated .tgz files are:
- Versioned artifacts based on
Chart.yaml - The unit of distribution for Helm charts
- The only files that should be uploaded to a repository (e.g., Nexus)
Helm uses the version field from Chart.yaml to name and manage these packages:
version: 1.0.4This means:
Chart version → defines the package version
.tgz file → immutable artifact
Key Principle

Never push raw chart directories to a repository.
Only packaged (.tgz) charts should be published.
This ensures:
- Reproducibility
- Version traceability
- Compatibility with Helm repositories
6.2 Upload to Nexus
curl -u usr_jenkins_nexus:1234qwer --upload-file devops-api-1.0.4.tgz https://nexus.devops-db.internal/repository/helm-devops-db/
curl -u usr_jenkins_nexus:1234qwer --upload-file iplocation-api-1.0.5.tgz https://nexus.devops-db.internal/repository/helm-devops-db/
curl -u usr_jenkins_nexus:1234qwer --upload-file totp-api-1.0.6.tgz https://nexus.devops-db.internal/repository/helm-devops-db/
6.3 Add Repository
helm repo add devops-db https://nexus.devops-db.internal/repository/helm-devops-db/
helm repo update
helm search repo devops-db
6.4 Result
"devops-db" has been added to your repositories
Hang tight while we grab the latest from your chart repositories...
Update Complete. ⎈Happy Helming!⎈
NAME CHART VERSION APP VERSION DESCRIPTION
devops-db/devops-api 1.0.4 1.1.2 A Helm chart for Kubernetes
devops-db/iplocation-api 1.0.5 1.0.0 A Helm chart for iplocation api
devops-db/totp-api 1.0.6 1.0.0 A Helm chart for TOTP API Validator7. Platform Chart (Composition Layer)
Repository:
https://gitlab.devops-db.internal/infrastructure/helms/devops-platform
7.1 Chart.yaml
apiVersion: v2
name: devops-platform
description: DevOps Platform - multi-service deployment
type: application
version: 1.0.0
dependencies:
- name: devops-api
version: 1.0.4
repository: https://nexus.devops-db.internal/repository/helm-devops-db/
- name: iplocation-api
version: 1.0.5
repository: https://nexus.devops-db.internal/repository/helm-devops-db/
- name: totp-api
version: 1.0.6
repository: https://nexus.devops-db.internal/repository/helm-devops-db/7.2 Resolve Dependencies
helm dependency update .
Hang tight while we grab the latest from your chart repositories...
Update Complete. ⎈Happy Helming!⎈
Saving 3 charts
Downloading devops-api from repo https://nexus.devops-db.internal/repository/helm-devops-db/
Downloading iplocation-api from repo https://nexus.devops-db.internal/repository/helm-devops-db/
Downloading totp-api from repo https://nexus.devops-db.internal/repository/helm-devops-db/
Deleting outdated charts
7.3 Verify
helm dependency list
NAME VERSION REPOSITORY STATUS
devops-api 1.0.4 https://nexus.devops-db.internal/repository/helm-devops-db/ ok
iplocation-api 1.0.5 https://nexus.devops-db.internal/repository/helm-devops-db/ ok
totp-api 1.0.6 https://nexus.devops-db.internal/repository/helm-devops-db/ ok
8. Deployment
helm upgrade --install devops-platform . -n devops-api --create-namespace
8.1 Result
STATUS: deployed
REVISION: 1
8.2 Pods
kubectl get pods -n devops-api
devops-platform-devops-api
devops-platform-iplocation-api
devops-platform-totp-api
9. Validation
TOTP API
curl "http://totp-api.devops-db.internal/totp?secret=TRKQWWZUCB2ZK6WMAJQ7GVJNVVNGHWF4"
{"totp":"535745"}IP Location
curl "http://iplocation-api.devops-db.internal/location?ip=149.90.79.135"
{"query":"149.90.79.135","country":"Portugal","city":"Porto"}
DevOps API
curl -s "http://devops-api.devops-db.internal/tags?repo=services/reporting&sort=date" | jq
[
"v0.1.0",
"v0.1.1",
"v0.2.0",
"v0.2.1",
"v0.2.2",
"v0.3.0",
"v0.3.1",
"v0.4.0",
"v0.4.1",
"v0.5.0",
"v0.6.0",
"v0.7.0",
"v0.8.0",
"v0.9.0",
"v0.9.1",
"v1.0.0",
"v1.1.0",
"v1.2.0",
"v1.2.1",
"v1.3.0"
]All services respond correctly, proving that:
✔ dependencies resolved
✔ values applied correctly
✔ templates executed as expected
10. Final Architecture Insights
10.1 Subcharts Are Not YAML Reuse
They are independently versioned components
composed into a system
10.2 Naming Strategy Is Critical
Without consistent naming → collisions
With proper naming → scalable architecture
10.3 Values Drive Everything
Templates are static
Values define behavior
10.4 Platform Chart Role
Defines system composition
Controls versions
Manages shared configuration
Conclusion
This architecture transforms Helm from a simple templating tool into:
a system composition engine for Kubernetes platforms
By combining:
- Independent service charts
- A central platform chart
- A proper naming strategy
- Structured values
✔ scalability
✔ reusability
✔ maintainability
✔ production readiness
