Best way to use Bitnami’s database Helm charts

In this article I will show how to include Bitnami's charts as dependencies and use them in your own Helm templates in the easiest way.

Best way to use Bitnami’s database Helm charts
Photo by Campaign Creators / Unsplash

Bitnami provides the most secure Helm charts when it comes to installing databases like MySQL, PostgreSQL or MariaDB inside of your Kubernetes cluster. In this article I will show the best way to include these charts as dependencies and use the values, such as hostname, database name, username and password, in your own templates in the easiest possible and most stable way.

TL;DR

💡
Use the supplied named templates and standardized structure of the Values object to define your own named templates, which are referenced in the templates of your own Helm chart. 

Why you should use Bitnami’s charts

The Bitnami project is known for providing highly secure software packages that keep pace with the development of the upstream software. Their charts and containers come with better security settings out of the box, thus improving the quality of your own charts. They are also known for good documentation and an active community in case of problems.


How to define a dependency

With Helm it is very easy to define other Helm charts as dependencies and to install them together with your own Helm chart. The definition of the dependence takes place thereby in the file Chart.yaml in the section dependencies.

At least the name of the Helm chart (name), as well as the desired version (version) must be indicated. In the case of the Bitnami Helm charts, we also need the URL of the repository (repository) where the charts are stored.

Helpful is also a possibility to activate or deactivate the use of the dependency. For this you define a value - in the example mysql.enabled - of the type boolean in the values.yaml, which can then be referenced under condition.

For the MySQL Helm chart in version 9.4.1 it looks like this:

dependencies:
  - name: mysql
    version: 9.4.1
    repository: https://charts.bitnami.com/bitnami
    condition: mysql.enabled

Configuring the defined dependency

All attributes available to us for configuring the dependent Helm chart can be set by prepending the name of the Helm chart. The MySQL Helm chart dependency defined above has an attribute architecture that we can set via mysql.architecture.

In case an alias has been defined, we have to prepend it instead of the name.

dependencies:
  - name: mysql
    alias: mysqldep
    version: 9.4.1
    repository: https://charts.bitnami.com/bitnami
    condition: mysqldep.enabled

In the example, mysql.architecture then becomes mysqldep.architecture.

Support of multiple databases

Some applications support different databases as backend. In such a situation, the user of our Helm chart should be able to select the desired database. For this we define the supported databases as dependency as usual:

dependencies:
  - name: mysql
    version: 9.4.1
    repository: https://charts.bitnami.com/bitnami
    condition: mysql.enabled
  - name: postgresql
    version: 11.9.10
    repository: https://charts.bitnami.com/bitnami
    condition: postgresql.enabled

However, we should be careful not to activate both dependencies at the same time. Since there is no native mechanism for this in Helm, we have to be content with issuing the user a small warning after installation, which can be specified in the NOTES.txt file.

If multiple dependencies are enabled, we have to decide during the development of the Helm chart which of the databases we consider to be the "right" database and use it for the configuration of our application. In the example MySQL is used, but this can of course be completely freely defined.

{{- if and (.Values.mysql.enabled) (.Values.postgresql.enabled) }}
! WARNING !
  You have defined the usage of multiple database dependencies using mysql.enabled and postgresql.enabled!
  This Helm chart will use MySQL as the primary database.
! WARNING !
{{- end }}

We should keep this order we have established consistent throughout the definition of our Helm chart and also keep it in mind when defining our Named Templates (see Definition of own Named Templates).


Structure of the Values object by Bitnami

Since we want to use the variables of the database charts in our chart, we will first look at the structure of the Values object. The auth section, which is relevant for us, is structured identically for all the charts mentioned and implemented as follows:

Value
Description
auth.database Name for a custom database to create
auth.existingSecret Name of existing secret to use for credentials
auth.password Password for the custom user to create. Ignored if auth.existingSecret is provided.
auth.username Name for a custom user to create

The PostgreSQL chart takes a special role here, since it additionally defines these variables under the section global.postgresql.auth. Their definition overwrites the value that is set under auth. Fortunately for us, this Helm chart provides Named Templates that abstract this logic and therefore we only need to reference these named templates in our templates instead of the variables.

Bitnami’s provided Named Templates

All of the mentioned charts come with some handy Named Templates pre-defined, that we can simply reference inside of our own Helm templates. Here a list of the once I usually use inside of my own charts, from the MySQL chart:

Named Template
Description
mysql.primary.fullname Hostname of the database
mysql.secretName Name of the secret containing the database password

The chart for PostgreSQL provides additional Named Templates we can use:

Named Template
Description
postgresql.database The name of the custom database
postgresql.password The password to use for authentication
postgresql.username The username to use for authentication

Referencing the Values in our Templates

We could now use this value structure and Named Templates in our own charts, for example, to define an environment variable of a container. To do this, we need the name of the variable or the Named Template, as well as the name of or the alias we gave the dependency in Chart.yaml. If we have not set the alias attribute, we simply use the name of the chart:

dependencies:
  - name: mysql
    version: 9.4.1
    repository: https://charts.bitnami.com/bitnami
    alias: mysqldep
    condition: mysqldep.enabled

Excerpt of a pod definition:

kind: Pod
spec:
  containers:
  - env:
    - name: DATABASE
      value: {{ .Values.mysqldep.auth.database }} # referencing the variable directly
    - name: DATABASE_HOST
      value: {{ include "mysql.primary.fullname" .Subcharts.mysqldep }} # referencing the Named Template mysql.primary.fullname of the dependency named mysqldep
...

This works fine as it is, but what happens if the user does not want to use the dependencies and instead wants to specify an existing database? For this we would first need an if-condition in our templates, which checks the defined condition. Depending on the outcome of the check, values and named templates of the dependency (as shown in the example above) or the values of the existing database are used for the definition of the environment variables.

However, to define the values of this existing database, we still need a structure in our own Values Object, which we want to define in the next step.


Structure of our own Values Object

I recommend defining a section called externalDatabase with the following attributes for this purpose:

externalDatabase:
  database:
  existingSecret:
  hostname:
  password:
  username:
mysqldep:
  ...
postgresql:
  ...

We will use the values for the external database as soon as none of the required dependencies has been activated via the respective enabled attribute.

Additionally, one could imagine the attributes port and type, if the application supports different databases and we need to pass the selected database type and the corresponding port.

Definition of own Named Templates

We have now created the structure in our Values object and can use this together with the Named Templates of the Bitnami charts to define our own Named Templates. This is done in the templates folder in a file with the extension .tpl. Here is an example of a named template that gives us the hostname of the database:

{{/*
Return the hostname of the database to use
*/}}
{{- define "database.hostname" -}}
  {{- if .Values.mysqldep.enabled -}}
    {{- printf "%s" (include "mysql.primary.fullname" .Subcharts.mysqldep) -}}
  {{- else if .Values.postgresql.enabled -}}
    {{- printf "%s" (include "postgresql.primary.fullname" .Subcharts.postgresql) -}}
  {{- else -}}
    {{- printf "%s" (tpl .Values.externalDatabase.hostname $) -}}
  {{- end -}}
{{- end -}}

Note here that the - possibly - defined alias only affects the designation of the referenced Values (line 2) and the Subcharts (line 3), but not the designation of the Named Template (also line 3), since this designation is defined in the referenced Helm chart.

All the information we need for use in our Helm chart is obtained from the following Named Templates:

Named Template
Description
database.hostname The hostname of the database
database.name The name of the custom database
database.username The username to use for authentication
database.secretName The name of the secret containing the password for authentication
database.passwordKey The key where the password is stored inside of the secret

These could look like the following as an example:

{{/*
Return the name for the database to use
*/}}
{{- define "database.name" -}}
  {{- if .Values.mysqldep.enabled -}}
    {{- printf "%s" (tpl .Values.mysqldep.auth.database $) -}}
  {{- else if .Values.postgresql.enabled }}
    {{- printf "%s" (include "postgresql.database" .Subcharts.postgresql) -}}
  {{- else -}}
    {{- printf "%s" (tpl .Values.externalDatabase.database $) -}}
  {{- end -}}
{{- end -}}
{{/*
Return the name for the user to use
*/}}
{{- define "database.username" -}}
  {{- if .Values.mysqldep.enabled -}}
    {{- printf "%s" (tpl .Values.mysqldep.auth.username $) -}}
  {{- else if .Values.postgresql.enabled -}}
    {{- printf "%s" (include "postgresql.username" .Subcharts.postgresql) -}}
  {{- else -}}
    {{- printf "%s" (tpl .Values.externalDatabase.username $) -}}
  {{- end -}}
{{- end -}}
{{/*
Get the name of the secret containing the database password .
*/}}
{{- define "database.secretName" -}}
  {{- if .Values.mysqldep.enabled -}}
    {{- printf "%s" (include "mysql.secretName" .Subcharts.mysqldep) -}}
  {{- else if .Values.postgresql.enabled -}}
    {{- printf "%s" (include "postgresql.secretName" .Subcharts.postgresql) -}}
  {{- else if .Values.externalDatabase.existingSecret }}
    {{- printf "%s" (tpl .Values.externalDatabase.existingSecret $) -}}
  {{- else -}}
    {{- printf "%s" (include "mychart.fullname" .) -}}
  {{- end -}}
{{- end -}}

Here mychart.fullname references the default Named Template created when using the helm create mychart command. Of course you have to replace all of the occurrences of mychart with your real chart name.

{{/*
Get the user-password key for the database password
*/}}
{{- define "database.passwordKey" -}}
  {{- if .Values.mysqldep.enabled -}}
    {{- "mysql-password" -}}
  {{- else if .Values.postgresql.enabled -}}
    {{- printf "%s" (include "postgresql.userPasswordKey" .Subcharts.postgresql) -}}
  {{- else if .Values.externalDatabase.userPasswordKey -}}
    {{- printf "%s" (tpl .Values.externalDatabase.userPasswordKey $) -}}
  {{- else -}}
    {{- "password" -}}
  {{- end -}}
{{- end -}}

Usage in your own templates

In the following the exemplary use of the Named Templates and variables in an own template. Of course, the names of the environment variables must be adapted to your concrete application:

kind: Pod
spec:
  containers:
  - env:
    - name: DATABASE_HOSTNAME
      value: {{ include “database.hostname“ . | quote }}
    - name: DATABASE
      value: {{ include “database.name“ . | quote }}
    - name: USERNAME
      value: {{ include “database.username“ . | quote }}
    - name: PASSWORD
      valueFrom: 
        secretKeyRef:
          name: {{ include “database.secretName“ . | quote }}
          key: {{ include “database.passwordKey“ . | quote }}

Finally we also need to create a Secret, for the case an existing database should be used and the password is given via externalDatabase.password.

{{- if and (not .Values.mysqldep.enabled) (not .Values.postgresql.enabled) (not .Values.externalDatabase.existingSecret) -}}
apiVersion: v1
kind: Secret
metadata:
  name: {{ include "mychart.fullname" . }}
  labels:
    {{- include "mychart.labels" . | nindent 4 }}
data:
  {{- include "database.userPasswordKey" . | nindent 2 }}: {{ .Values.externalDatabase.password | b64enc }}
{{- end }}

And there you go. You now know a flexible and efficient way to use the existing Bitnami Helm charts for databases in your own Helm chart.