You probably do need to "create a chart" with a separate Chart.yaml file for each service, but you can move the actual implementation out to a shared library chart.
Write a normal Helm chart that has the 85%-common parts. You can test deploying it if you'd like, but it will probably take most of the interesting parts as values. Make sure this is accessible somewhere; the two easiest options will be in a chart repository or as part of a monorepo.
Now your service chart just needs to include to include the shared chart. Using Helm 3 syntax, this could look like:
apiVersion: 2 # Helm 3 syntax with inline requirements
name: some-service
version: 0.0.1
appVersion: 20210317
dependencies:
- name: application # the shared chart
version: '^1.0'
repository: 'http://helm.example.com/charts'
In the service chart's values.yaml file, you need to make sure to include to shared-chart settings under a key matching the name: (or, if the shared chart is so configured, global:):
application:
redis:
enabled: false
If you're depending on the settings in the Chart.yaml file, the shared-library chart will see its own Chart.yaml as .Chart, which can affect generated names (if you name things {{ .Release.Name }}-{{ .Chart.Name }}-suffix they will see the library's .Chart.Name, not the service's).
The service chart doesn't actually need its own templates, and I believe the chart will install correctly if the templates directory isn't there. You can add more YAML in templates if you need to. The harder part is suppressing or modifying what's in the library chart, and mostly that will need to support every possible option a service might need. (You can wrap an entire YAML file in {{ if }}...{{ end }}, or include multiple YAML objects in the same file separated by ---, which can both help provide this kind of option.)