Using GitHub Repositories as Helm Registries

Hosting Helm charts can be slightly annoying, as one needs to maintain an extra piece of infrastructure that does practically nothing, or pay for someone else to do it for you.

This article describes a workaround, which uses a standard private repository as a helm chart, which is then accessible via the helm CLI, ArgoCD, GitHub Actions and more.

To provide some more context, my use case was that of publishing helm charts from private GitHub repositories using GitHub Actions, and that’s what we’re going to look at today.

Quick sidenote: you may alternatively set up a public repository, which allows everyone to get read access to it.

Prerequisites

You’ll need a Personal Access Token to access the helm registry. Check the documentation on how to create one.

Setting Up

We start by creating a simple private repository, where all our charts will be stored.

Screen Shot 2022-07-03 at 13.27.16

Create a blank index.yaml file in the root of the repository (e.g. touch index.yaml).

Note: We are creating it in the root directory in the main branch, but you can also use a subfolder/branch as the registry. You will just need to change the URL when you access the registry, changing the $BRANCH part of the URL and appending the directory.

You can now add the repository as a helm registry using the command line:

1
helm repo add helm-registry 'https://$TOKEN@raw.githubusercontent.com/$USERNAME/$REPO/$BRANCH'

For example, with a token ghp_xxxxxxxx, to access the branch main on the joaomlneto/helm repository:

1
2
$ helm repo add helm-registry 'https://ghp_xxxxxxxx@raw.githubusercontent.com/joaomlneto/helm/main'
"helm-registry" has been added to your repositories

You can confirm it is added by running helm repo list:

1
2
3
4
$ helm repo list
NAME          	URL
…
helm-registry 	https://ghp_xxxxxxxx@raw.githubusercontent.com/joaomlneto/helm/main

Publishing

I assume you already have a packaged chart. If not, to create a chart you can use helm create <name> and to package it you can run helm package <name>.

Publishing a chart to the repository requires a small workaround, as we can’t use helm push. Instead, we’ll just commit the changes manually:

  1. Clone the repository (e.g. git clone https://github.com/joaomlneto/helm.git)
  2. Copy your packaged chart to the repository (cp <chart.tgz> <repository root>)
  3. Update the index.yaml by running helm repo index . on the root of the repository
  4. Commit and push the changes (git add . && git commit -m "Add <name> v0.1.0" && git push)

image-20220704140755956

It may take up to 5 minutes (as contents from rawgithubusercontent.com are cached), but the chart should then be visible if you run helm repo update, followed by helm search repo <name>.

1
2
3
4
5
6
7
8
9
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
…
...Successfully got an update from the "helm-registry" chart repository
…
Update Complete. ⎈Happy Helming!⎈
$ helm search repo testchart
NAME                   	CHART VERSION	APP VERSION	DESCRIPTION
helm-registry/<name>   	0.1.0        	1.16.0     	A Helm chart for Kubernetes

GitHub Actions

Publishing the artifact via GitHub Actions is the same — you clone the chart repository, copy the packaged chart file, update the index and then commit the changes.

I suggest splitting the chart generation and chart publishing in two separate jobs, and storing your Packages Access Token in an Encrypted Secret.

This is an example job of how you can generate the chart:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
jobs:
  
  package:
    name: Package Helm Chart
    runs-on: ubuntu-latest
    outputs:
      chart_filename: ${{ steps.chart_filename.outputs.chart_filename }}
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v3
        
      - name: Install Helm
        uses: azure/setup-helm@v2.1
        with:
          version: v3.5.2

      - name: Package Helm Charts
        run: helm package chart

      - id: chart_filename
        name: Output Chart Filename
        run: echo "::set-output name=chart_filename::$(ls *.tgz)"

      - name: Upload Helm Chart Package as Workflow Artifact
        uses: actions/upload-artifact@v3
        with:
          name: ${{ steps.chart_filename.outputs.chart_filename }}
          path: ${{ steps.chart_filename.outputs.chart_filename }}
          retention-days: 1
          if-no-files-found: error

This job will package the chart and upload it as a workflow artifact. This can be later accessed by our publish job:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
jobs:
  
  publish:
    name: Publish Helm Chart
    runs-on: ubuntu-latest
    needs:
      - package
    steps:
      - name: Checkout Chart Repository
        uses: actions/checkout@v3
        with:
          repository: joaomlneto/helm /* CHANGE ME!!! */
          token: ${{ secrets.PACKAGES_ACCESS_TOKEN }}

      - name: Configure Git
        run: |
          git config user.name "$GITHUB_ACTOR"
          git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
      - name: Install Helm
        uses: azure/setup-helm@v2.1
        with:
          version: v3.5.2

      - name: Retrieve Chart Package Workflow Artifact
        uses: actions/download-artifact@v3
        with:
          name: ${{needs.package.outputs.chart_filename }}

      - name: Add chart to repository
        run: |
          helm repo index .
          git add .
          git commit -m "Add ${{ needs.package.outputs.chart_filename }}"
          git push

Conclusion

This article showed that, with very little compromises, we are able to use a vanilla private GitHub repository to emulate a private Helm registry. If something’s amiss, please do let me know!