This project demonstrates a production-friendly Helm layout for multiple applications with file-based application config, service-to-service wiring, and shared PVCs.
helm-network-example/
├── charts/
│ ├── application/ # Reusable chart used for app1-app4
│ │ ├── files/ # Literal app-owned config files
│ │ │ ├── app1/
│ │ │ ├── app2/
│ │ │ ├── app3/
│ │ │ └── app4/
│ │ └── templates/
│ ├── command/ # 5th app imported as dependency
│ └── platform/ # Umbrella chart wiring app1-app4 + command
├── environments/
│ └── prod/
│ └── values.yaml
└── scripts/
The reusable application chart has this helper in _helpers.tpl:
{{ .Release.Name }}-{{ .Values.app.name }}
So installing the platform release as demo creates resources named:
demo-app1
demo-app2
demo-app3
demo-app4
demo-command
Application-owned config files live under:
charts/application/files/app1/
charts/application/files/app2/
charts/application/files/app3/
charts/application/files/app4/
These files are treated as literal application config. They are not where Helm topology should live.
Helm deployment wiring lives in values:
network:
talksTo:
- app2The reusable chart converts that wiring into environment variables in the Deployment:
APP2_URL=http://demo-app2:80
This keeps app-config.yaml focused on what the application owns and keeps service discovery/release-scoped naming inside Helm.
Example:
configFiles:
enabled: true
path: files/app1
mountPath: /etc/app/configThis mounts app1's config files at:
/etc/app/config/app-config.yaml
/etc/app/config/routing.yaml
/etc/app/config/feature-flags.yaml
The network.talksTo values model this topology:
app1 -> app2
app2 -> app3, app4
app4 -> app1, app2, app3
For release demo, the chart injects:
app1: APP2_URL=http://demo-app2:80
app2: APP3_URL=http://demo-app3:80, APP4_URL=http://demo-app4:80
app4: APP1_URL=http://demo-app1:80, APP2_URL=http://demo-app2:80, APP3_URL=http://demo-app3:80
The platform chart creates two PVCs:
shared-data
shared-cache
Mount usage:
app3 mounts shared-data and shared-cache
app4 mounts shared-data
app2 gets a writable runtime directory using emptyDir:
/var/lib/app2/workdir
This keeps read-only file config in ConfigMaps and writable runtime directories in pod volumes.
helm dependency build ./charts/platform
helm template demo ./charts/platform --namespace demo --values ./environments/prod/values.yamlOr use:
./scripts/template.shhelm dependency build ./charts/platform
helm upgrade --install demo ./charts/platform \
--namespace demo \
--create-namespace \
--values ./environments/prod/values.yamlOr use:
./scripts/install.sh- ConfigMap files should stay reasonably small. Kubernetes ConfigMaps have a size limit.
- Do not put secrets in these config files. Use Kubernetes Secrets, External Secrets Operator, SOPS, Sealed Secrets, or your cluster's secret-management standard.
- Keep
values.yamlfocused on deployment wiring: image, ports, replicas, mounts, resources, probes, service relationships. - Keep actual application config file contents under
charts/application/files/<app-name>/.
PersistentVolumeClaims in this example are intentionally not prefixed with the Helm release name.
The PVC metadata.name rendered by charts/platform/templates/pvc.yaml is the exact name used by application claimName mounts.
For example, this value:
globalPersistentVolumeClaims:
- name: shared-datarenders a PVC named shared-data, and app mounts should use:
persistence:
mounts:
- name: shared-data
claimName: shared-data
mountPath: /mnt/shared-dataApplication Deployments and Services still use the {{ .Release.Name }}-appname naming helper.
PVCs do not, because PVCs are shared storage objects and claimName must match the real PVC name exactly.
configFiles.mounts mounts the whole generated app ConfigMap directory in one or more locations.
configFiles.fileMounts mounts a single config file to a specific path using subPath.
Use env for simple variables, extraEnv for valueFrom, and envFrom for whole ConfigMap/Secret imports.