Helm Your Kubernetes Application

Shahin Mahmud
The Zeals Tech Blog
15 min readFeb 8, 2023

At Zeals, most of our workloads run on Kubernetes including our own application and open-source application. We use helm extensively to manage and automate the lifecycle of our application. In this article, we are going to discuss, how helm works internally and how we can make a helm chart for an application.

What is helm?

Helm is the package manager for Kubernetes. Just like any other package manager, Helm lets us package and share our own Kubernetes app and find, install, upgrade, and manage other available Kubernetes apps.

Helm packages are called charts. A helm package registry is called a helm repository. A repository can host one or many charts and it can be hosted anywhere by following the protocol that the helm cmd client understands.

And an installed helm chart (package) on a Kubernetes cluster is called helm release.

In this article, we are going to use helm v3

Why Helm?

Kubernetes applications often contain several Kubernetes resources like configmaps, secrets, roles, deployments, crds, and many others. And oftentimes, some values need to be shared across resources. Helm provides a templating option, making it easy to change the config and manage applications on Kubernetes.

Also, it keeps the history of releases and has an easy option to roll back to a previous version.

How to use helm?

At first, we need to add the helm repository that contains our desired chart to the local index. This will add all the chart-related information on that repository to the local index.

$helm repo add bitnami https://charts.bitnami.com/bitnami

$helm search repo bitnami
NAME CHART VERSION APP VERSION DESCRIPTION
bitnami/airflow 14.0.7 2.5.0 Apache Airflow is a tool to express and execute...
bitnami/apache 9.2.11 2.4.55 Apache HTTP Server is an open-source HTTP serve...
bitnami/appsmith 0.1.9 1.9.2 Appsmith is an open source platform for buildin...
bitnami/argo-cd 4.4.2 2.5.7 Argo CD is a continuous delivery tool for Kuber...
bitnami/argo-workflows 5.1.2 3.4.4 Argo Workflows is meant to orchestrate Kubernet...
bitnami/aspnet-core 4.0.2 7.0.2 ASP.NET Core is an open-source framework for we...
bitnami/cassandra 10.0.0 4.1.0 Apache Cassandra is an open source distributed ...
bitnami/etcd 8.7.2 3.5.6 etcd is a distributed key-value store designed ...
........

Let's install Etcd by using the bitnami/etcd helm chart!
we can see all information about the chart by

$helm show all bitnami/etcd 

We can also see the details here —
https://github.com/bitnami/charts/tree/main/bitnami/etcd

Chart.yaml contains general info about the chart.
templates folder contains various templates and when rendered with values, it generates Kubernetes manifests which get deployed.
values.yaml contains all the default values to install etcd with default values provided by the chart.

$kubectl create ns etcd

$helm install etcd bitnami/etcd -n etcd
NAME: etcd
LAST DEPLOYED: Thu Jan 19 12:55:45 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: etcd
CHART VERSION: 8.7.2
APP VERSION: 3.5.6

** Please be patient while the chart is being deployed **

etcd can be accessed via port 2379 on the following DNS name from within your cluster:

etcd.default.svc.cluster.local

To create a pod that you can use as a etcd client run the following command:

kubectl run etcd-client --restart='Never' --image docker.io/bitnami/etcd:3.5.6-debian-11-r21 --env ROOT_PASSWORD=$(kubectl get secret --namespace default etcd -o jsonpath="{.data.etcd-root-password}" | base64 -d) --env ETCDCTL_ENDPOINTS="etcd.default.svc.cluster.local:2379" --namespace default --command -- sleep infinity

Then, you can set/get a key using the commands below:

kubectl exec --namespace default -it etcd-client -- bash
etcdctl --user root:$ROOT_PASSWORD put /message Hello
etcdctl --user root:$ROOT_PASSWORD get /message

To connect to your etcd server from outside the cluster execute the following commands:

kubectl port-forward --namespace default svc/etcd 2379:2379 &
echo "etcd URL: http://127.0.0.1:2379"

* As rbac is enabled you should add the flag `--user root:$ETCD_ROOT_PASSWORD` to the etcdctl commands. Use the command below to export the password:

export ETCD_ROOT_PASSWORD=$(kubectl get secret --namespace default etcd -o jsonpath="{.data.etcd-root-password}" | base64 -d)

Let's see the helm release and deployed Kubernetes resources-

# list all the installed charts(release)
$helm list -n etcd
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
etcd etcd 1 2023-01-19 13:10:39.745863 +0600 +06 deployed etcd-8.7.2 3.5.6

# kub resources
$kubectl get all -n etcd
NAME READY STATUS RESTARTS AGE
pod/etcd-0 1/1 Running 0 107s

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/etcd ClusterIP 10.0.15.160 <none> 2379/TCP,2380/TCP 107s
service/etcd-headless ClusterIP None <none> 2379/TCP,2380/TCP 107s

NAME READY AGE
statefulset.apps/etcd 1/1 109s

We can change the default values by providing our own values and helm will merge the values that we set with the default values present in the chart and render Kubernetes manifests accordingly.

Let’s change the root password by creating new values.yaml

auth:
rbac:
rootPassword: "shahin"
# To see what kubernetes manifest would be generated after merging values
# helm upgrade {release_name} {chartname} -f {path/values.yaml}
$helm upgrade etcd bitnami/etcd -f values.yaml -n etcd --debug --dry-run
.... rendered kubernetes manifest

# upgrade
$helm upgrade etcd bitnami/etcd -f values.yaml -n etcd
Release "etcd" has been upgraded. Happy Helming!
NAME: etcd
LAST DEPLOYED: Thu Jan 19 14:12:25 2023
NAMESPACE: etcd
STATUS: deployed
REVISION: 2 # with each upgrade, revision increases
TEST SUITE: None
NOTES:
CHART NAME: etcd
CHART VERSION: 8.7.2
APP VERSION: 3.5.6
.......

It's possible to provide multiple values.yaml. While rendering a template, the last provided value will take precedence. We can also use
--set key=value with or without values.yaml.

We can see the list of installed charts (releases) on the cluster with the following:

# show all releases in all namespaces
$helm list -A

# show all releases in etcd namespace
# helm list -n {namespace}
$helm list -n etcd
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
etcd etcd 2 2023-01-22 16:05:52.983818 +0600 +06 deployed etcd-8.7.2 3.5.6

We can roll back to a previous revision which will deploy the previous version of Kubernetes manifests to the cluster. Uninstalling the release will delete every Kubernetes resource related to that release.

# rollback
$helm rollback etcd 1

#uninstall
$helm uninstall etcd

How does helm work?

Template
Templating is about processing text. In a template, you can define text with a value placeholder, import other templates, and process input value with functions the templating engine understands. When templating engine renders that text with values it generates raw text data. Helm templating engine is built on top of go templating.
For example:

template = `My name is {{.Name}}`

If we process template with Name = Shahin
we will get -
My name is Shahin

Chart
A chart contains templates for Kubernetes manifests and helper templates. Also, it has default values for the templates. Later when installing the chart, helm renders templates with the default values and user-provided values to Kubernetes manifests.

https://github.com/bitnami/charts/blob/main/bitnami/etcd/templates/configmap.yaml
It’s importing helper templates, processing, and using values:

{{- if (include "etcd.createConfigmap" .) }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ printf "%s-configuration" (include "common.names.fullname" .) | trunc 63 | trimSuffix "-" }}
namespace: {{ .Release.Namespace | quote }}
labels: {{- include "common.labels.standard" . | nindent 4 }}
{{- if .Values.commonLabels }}
{{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }}
{{- end }}
{{- if .Values.commonAnnotations }}
annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }}
{{- end }}
data:
etcd.conf.yml: |-
{{- include "common.tplvalues.render" ( dict "value" .Values.configuration "context" $ ) | nindent 4 }}
{{- end }}

When a chart is packaged, it's compressed into a single
{chart_name-version}.tgz file like etcd-8.7.2.tgz.

Repository:
A helm repository hosts a file named index.yaml which contains information about all the charts that the repository has.
Chart information includes the chart name, version, metadata, and URL of the packaged chart to download.

bitnami repository index.yaml

etcd:
- annotations:
category: Database
apiVersion: v2
appVersion: 3.5.4
created: "2022-06-06T21:50:10.120044659Z"
dependencies:
- name: common
repository: https://charts.bitnami.com/bitnami
tags:
- bitnami-common
version: 1.x.x
description: etcd is a distributed key-value store designed to securely store
data across a cluster. etcd is widely used in production on account of its reliability,
fault-tolerance and ease of use.
digest: sha256:a522643668d04c32c56770e45c3e0752ac153530910bc2b4e5127d493e99b9a8
home: https://github.com/bitnami/charts/tree/master/bitnami/etcd
icon: https://bitnami.com/assets/stacks/etcd/img/etcd-stack-220x234.png
keywords:
- etcd
- cluster
- database
- cache
- key-value
maintainers:
- name: Bitnami
url: https://github.com/bitnami/charts
name: etcd
sources:
- https://github.com/bitnami/bitnami-docker-etcd
- https://coreos.com/etcd/
urls:
- https://charts.bitnami.com/bitnami/etcd-8.2.5.tgz # chart url
version: 8.2.5 # chart version
- annotations:
category: Database
apiVersion: v2
appVersion: 3.5.4
created: "2022-06-03T15:32:47.628296228Z"
dependencies:
- name: common
repository: https://charts.bitnami.com/bitnami
tags:
- bitnami-common
version: 1.x.x
description: etcd is a distributed key-value store designed to securely store
data across a cluster. etcd is widely used in production on account of its reliability,
fault-tolerance and ease of use.
digest: sha256:dbde6c7608a046a64f689404b36ff8a04c2207aa0c138498db07deeea77dc8e2
home: https://github.com/bitnami/charts/tree/master/bitnami/etcd
icon: https://bitnami.com/assets/stacks/etcd/img/etcd-stack-220x234.png
keywords:
- etcd
- cluster
- database
- cache
- key-value
maintainers:
- name: Bitnami
url: https://github.com/bitnami/charts
name: etcd
sources:
- https://github.com/bitnami/bitnami-docker-etcd
- https://coreos.com/etcd/
urls:
- https://charts.bitnami.com/bitnami/etcd-8.2.4.tgz
version: 8.2.4

otherCharts:
- ........

Release
As we saw earlier, helm list can show us the installed releases in the Kubernetes cluster, which means it needs a backend to store the release information. For Helm3, it’s Kubernetes secrets. All the release is stored as Kubernetes secrets.

When we installed etcd chart with default values, it created a secret for that with the name sh.helm.release.v1.etcd.v1 , and when we upgraded it, it created sh.helm.release.v1.etcd.v2 . For each upgrade, it creates a new secret with the version part of the name incremented as v1, v2...vn

And when we do helm list it shows all releases from the latest versioned secrets.

$kubectl get secrets -n etcd
NAME TYPE DATA AGE
sh.helm.release.v1.etcd.v1 helm.sh/release.v1 1 13m # Initial relase
sh.helm.release.v1.etcd.v2 helm.sh/release.v1 1 12m # upgrade


$kubectl get secrets sh.helm.release.v1.etcd.v2 -o yaml -n etcd
apiVersion: v1
data:.....
kind: Secret
metadata:
creationTimestamp: "2023-01-22T10:05:59Z"
labels:
modifiedAt: "1674381962"
name: etcd # added a label by the relase name
owner: helm
status: deployed
version: "2"
name: sh.helm.release.v1.etcd.v2
namespace: etcd
resourceVersion: "4891"
uid: f9889029-f19e-4f80-a71c-694b6f2665b4
type: helm.sh/release.v1

Now, that we understand, how helm uses secrets to store releases, let’s see what a release is:

// ****** from the official codebase ****

// Lock is a lock file for dependencies.
//
// It represents the state that the dependencies should be in.
type Lock struct {
// Generated is the date the lock file was last generated.
Generated time.Time `json:"generated"`
// Digest is a hash of the dependencies in Chart.yaml.
Digest string `json:"digest"`
// Dependencies is the list of dependencies that this lock file has locked.
Dependencies []*Dependency `json:"dependencies"`
}

type Chart struct {
// Raw contains the raw contents of the files originally contained in the chart archive.
//
// This should not be used except in special cases like `helm show values`,
// where we want to display the raw values, comments and all.
Raw []*File `json:"-"`
// Metadata is the contents of the Chartfile.
Metadata *Metadata `json:"metadata"`
// Lock is the contents of Chart.lock.
Lock *Lock `json:"lock"`
// Templates for this chart.
Templates []*File `json:"templates"`
// Values are default config for this chart.
Values map[string]interface{} `json:"values"`
// Schema is an optional JSON schema for imposing structure on Values
Schema []byte `json:"schema"`
// Files are miscellaneous files in a chart archive,
// e.g. README, LICENSE, etc.
Files []*File `json:"files"`

parent *Chart
dependencies []*Chart
}

// Info describes release information.
type Info struct {
// FirstDeployed is when the release was first deployed.
FirstDeployed time.Time `json:"first_deployed,omitempty"`
// LastDeployed is when the release was last deployed.
LastDeployed time.Time `json:"last_deployed,omitempty"`
// Deleted tracks when this object was deleted.
Deleted time.Time `json:"deleted"`
// Description is human-friendly "log entry" about this release.
Description string `json:"description,omitempty"`
// Status is the current state of the release
Status Status `json:"status,omitempty"`
// Contains the rendered templates/NOTES.txt if available
Notes string `json:"notes,omitempty"`
// Contains the deployed resources information
Resources map[string][]runtime.Object `json:"resources,omitempty"`
}

// Release describes a deployment of a chart, together with the chart
// and the variables used to deploy that chart.
type Release struct {
// Name is the name of the release
Name string `json:"name,omitempty"`
// Info provides information about a release
Info *Info `json:"info,omitempty"`
// Chart is the chart that was released.
Chart *chart.Chart `json:"chart,omitempty"`
// Config is the set of extra Values added to the chart.
// These values override the default values inside of the chart.
Config map[string]interface{} `json:"config,omitempty"`
// Manifest is the string representation of the rendered template.
Manifest string `json:"manifest,omitempty"`
// Hooks are all of the hooks declared for this release.
Hooks []*Hook `json:"hooks,omitempty"`
// Version is an int which represents the revision of the release.
Version int `json:"version,omitempty"`
// Namespace is the kubernetes namespace of the release.
Namespace string `json:"namespace,omitempty"`
// Labels of the release.
// Disabled encoding into Json cause labels are stored in storage driver metadata field.
Labels map[string]string `json:"-"`
}

Release holds information like release_name, kubernetes manifests(rendered from template & values), version, release status, creation,deletion time,
chart's related file such as template, default values

All this data is turned into json and then compressed and stored as a Kubernetes secret.

Let’s check the decoded secrets created for an etcd release:

# full secret data
$kubectl get secrets sh.helm.release.v1.etcd.v2 -n etcd --template={{.data.release}} | base64 -D | base64 -D | gzip -d | jq
.... big json file .....

# release info data
$kubectl get secrets sh.helm.release.v1.etcd.v2 -n etcd --template={{.data.release}} | base64 -D | base64 -D | gzip -d | jq .info
{
"first_deployed": "2023-01-22T16:04:56.928624+06:00",
"last_deployed": "2023-01-22T16:05:52.983818+06:00",
"deleted": "",
"description": "Upgrade complete",
"status": "deployed",
"notes": "CHART NAME: etcd\nCHART VERSION: 8.7.2\nAPP VERSION: 3.5.6\n\n** Please be patient while the chart is being deployed **\n\netcd can be accessed via port 2379 on the following DNS name from within your cluster:\n\n etcd.etcd.svc.cluster.local\n\nTo create a pod that you can use as a etcd client run the following command:\n\n kubectl run etcd-client --restart='Never' --image docker.io/bitnami/etcd:3.5.6-debian-11-r21 --env ROOT_PASSWORD=$(kubectl get secret --namespace etcd etcd -o jsonpath=\"{.data.etcd-root-password}\" | base64 -d) --env ETCDCTL_ENDPOINTS=\"etcd.etcd.svc.cluster.local:2379\" --namespace etcd --command -- sleep infinity\n\nThen, you can set/get a key using the commands below:\n\n kubectl exec --namespace etcd -it etcd-client -- bash\n etcdctl --user root:$ROOT_PASSWORD put /message Hello\n etcdctl --user root:$ROOT_PASSWORD get /message\n\nTo connect to your etcd server from outside the cluster execute the following commands:\n\n kubectl port-forward --namespace etcd svc/etcd 2379:2379 &\n echo \"etcd URL: http://127.0.0.1:2379\"\n\n * As rbac is enabled you should add the flag `--user root:$ETCD_ROOT_PASSWORD` to the etcdctl commands. Use the command below to export the password:\n\n export ETCD_ROOT_PASSWORD=$(kubectl get secret --namespace etcd etcd -o jsonpath=\"{.data.etcd-root-password}\" | base64 -d)"
}

# default chart values
$kubectl get secrets sh.helm.release.v1.etcd.v2 -n etcd --template={{.data.release}} | base64 -D | base64 -D | gzip -d | jq .chart.values

# values we upgraded with
$kubectl get secrets sh.helm.release.v1.etcd.v2 -n etcd --template={{.data.release}} | base64 -D | base64 -D | gzip -d | jq .config
{
"auth": {
"rbac": {
"rootPassword": "shahin"
}
}
}

So, whenever we say helm creates, updates, list, and delete releases, it means it creates, updates, deletes, or list (by label selector) Kubernetes secrets.

What happens when helm repo add ?
Helm CLI appends repository info into a local host file named repository.yaml containing data about repository URL, authentication info, etc.
For example repository.yamlon my machine looks like this, as I have added two repositories so far — one is bitnami and another one is istio.

#{local_dir_path}/repository.yaml
apiVersion: ""
generated: "0001-01-01T00:00:00Z"
repositories:
- caFile: ""
certFile: ""
insecure_skip_tls_verify: false
keyFile: ""
name: bitnami
pass_credentials_all: false
password: ""
url: https://charts.bitnami.com/bitnami
username: ""
- caFile: ""
certFile: ""
insecure_skip_tls_verify: false
keyFile: ""
name: istio
pass_credentials_all: false
password: ""
url: https://istio-release.storage.googleapis.com/charts
username: ""

It also downloads the index.yaml file of the repository into the local cache directory.
For bitnami, helm created bitnami-index.yaml in the local cache directory.

How to find the location of repository.yaml and index.yaml files cache directory?

# depending the os, the location would varies
# for me, it's macos
$helm env
helm env
HELM_BIN="helm"
HELM_CACHE_HOME="/Users/**/Library/Caches/helm"
HELM_CONFIG_HOME="/Users/**/Library/Preferences/helm"
HELM_DATA_HOME="/Users/**/Library/helm"
HELM_DEBUG="false"
HELM_KUBEAPISERVER=""
HELM_KUBEASGROUPS=""
HELM_KUBEASUSER=""
HELM_KUBECAFILE=""
HELM_KUBECONTEXT=""
HELM_KUBETOKEN=""
HELM_MAX_HISTORY="10"
HELM_NAMESPACE="default"
HELM_PLUGINS="/Users/**/Library/helm/plugins"
HELM_REGISTRY_CONFIG="/Users/**/Library/Preferences/helm/registry.json"
# this where repository index.yaml and chart.tgz files are cached
HELM_REPOSITORY_CACHE="/Users/**/Library/Caches/helm/repository"
# repository.yaml file
HELM_REPOSITORY_CONFIG="/Users/**/Library/Preferences/helm/repositories.yaml"

How does helm CLI connect to Kubernetes clusters?
It uses the local kubeconfig file for connecting to Kubernetes clusters.

What happens when helm install release_name repository/chart?
Helm CLI merges the chart’s default value and user-provided value and renders the template which generates Kubernetes manifests.
It creates a release with all the data we discussed above with the initial release status as pending-install .

Then it deploys the generated Kubernetes manifests by calling the Kubernetes API server. Before deploying, it sorts the manifests. This is because if chart manifests create a namespace and resources under that namespace, the namespace should be deployed first.

// Those occurring earlier in the list get installed before those occurring later in the list.
{
"Namespace",
"NetworkPolicy",
"ResourceQuota",
"LimitRange",
"PodSecurityPolicy",
"PodDisruptionBudget",
"ServiceAccount",
"Secret",
"SecretList",
"ConfigMap",
"StorageClass",
"PersistentVolume",
"PersistentVolumeClaim",
"CustomResourceDefinition",
"ClusterRole",
"ClusterRoleList",
"ClusterRoleBinding",
"ClusterRoleBindingList",
"Role",
"RoleList",
"RoleBinding",
"RoleBindingList",
"Service",
"DaemonSet",
"Pod",
"ReplicationController",
"ReplicaSet",
"Deployment",
"HorizontalPodAutoscaler",
"StatefulSet",
"Job",
"CronJob",
"IngressClass",
"Ingress",
"APIService",
}

It then updates the release (Kubernetes secret) with a status and other information.

What happens when helm uinstall release_name ?
It queries the API server to get the secrets for that release by a label selector name: release_nameand it uses the secrets with the latest version number. As we discussed earlier, these secrets contain generated Kubernetes manifests and many other things.

It then does some sorting of manifests and then deletes those manifests by calling the Kubernetes API server. After that, the secret is updated or deleted depending on the user-provided flags like keep-history .

What happens when helm upgrade release_name chart_name ?
It fetches the last non-deleted release (Kubernetes secrets), merges user-provided value with existing release values, and generates manifest, incrementing the previous release version by 1.

It also checks for which new resources to be created comparing the new release manifests with the existing ones and whether there is any conflict. It then deploys the manifests and updates the release.

How to package and share our own application?

We are going to develop a helm chart for this -https://github.com/BackAged/tdset-operator and package and share it as GitHub self-hosted repository.

The application is a Kubernetes controller, it contains crd, roles, serviceaccount, rolebinding, deployment(controller) .

You can see the list of manifests we need to template — https://github.com/BackAged/tdset-operator/tree/main/example

If you would like to know how to write a Kubernetes operator -https://medium.com/@shahin-mahmud/write-your-first-kubernetes-operator-in-go-177047337eae

The basic workflow looks like this:

# creates a package
$helm create tdset

# add templates for all the manifest

# package the chart will generate a file chart_name-version.tgz
$helm package {chart_directory}


# create index.yaml for repository with valid url of the packaged chart for download
$helm repo index --url


# host your index.yaml and packaged chart

Here I have the charts handling all the manifests we need: https://github.com/BackAged/tdset-operator/tree/main/charts/tdset

Let's cover the service account template to see how templating works.

In the values.yaml we have this config. These are default values, during installation these values would be used if not overridden by user-provided values.

#values.yaml
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""

In the templates/serviceaccount.yaml we check whether serviceAccount.create value is set to true, only then generate service account manifests. We set the service account name as a user-provided value if none then the default value, we put this logic in a helper template and just include it in the manifest.

#templates/serviceaccount.yaml
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "tdset.serviceAccountName" . }}
labels:
{{- include "tdset.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
#templates/_helpers.tpl
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "tdset.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Create the name of the service account to use
*/}}
{{- define "tdset.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "tdset.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

We are going to share this chart in GitHub self-hosted repository using
https://github.com/helm/chart-releaser-action.

We need a charts/ directory containing all the charts that we are going to host in this repository and a branch: gh-pages and apply that branch as GitHub page source.

After adding the GitHub workflow using chart-releaser-action, on every push on master, it will check for chart version updates, if a new version is found, it will create a new release adding the packaged chart as an asset to the release and generate the index.yaml file with URL set to GitHub release asset.

https://github.com/BackAged/tdset-operator/blob/gh-pages/index.yaml

apiVersion: v1
entries:
tdset-controller:
- apiVersion: v2
appVersion: 0.0.5
created: "2023-01-22T19:51:04.627763337Z"
description: A Helm chart for Kubernetes
digest: e952796f92b3a8895f5c7ec3382ba5b13517232e65a1b21b37a76924fed932d2
name: tdset-controller
type: application
urls:
- https://github.com/BackAged/tdset-operator/releases/download/tdset-controller-0.1.0/tdset-controller-0.1.0.tgz
version: 0.1.0
generated: "2023-01-22T19:51:04.627776637Z"

Now we will be able to add this repository and install the chart using helm from anywhere.

# add the repository
$helm repo add tdset https://backaged.github.io/tdset-operator/

# install with default values
$helm install tdset tdset -n tdset

Hope you have enjoyed this so far!
Happy Helming!

--

--