How to Create and Host a Helm Chart Repository on GitHub Pages

Learn how to create a Helm chart repository hosted on GitHub Pages with automated CI/CD using GitHub Actions, chart-releaser, linting, and Kubernetes validation.

Managing Kubernetes applications at scale requires a reliable way to package, version, and distribute Helm charts. While managed solutions like Artifact Hub or cloud-hosted OCI registries exist, hosting your own Helm chart repository on GitHub Pages is a free, lightweight, and fully automated alternative that works well for teams of all sizes.

In this guide, we will walk through the complete process of setting up a self-hosted Helm chart repository using GitHub Pages and GitHub Actions. By the end, you will have a fully automated pipeline that lints, validates, packages, and publishes your Helm charts on every push to the main branch.

Prerequisites

Before getting started, make sure you have the following tools installed and configured:

  • Git - Version control. Install Git.
  • Helm 3 - The Kubernetes package manager. Follow the official Helm install guide for your operating system.
  • A GitHub account - You will need a repository to host the charts and GitHub Pages to serve them.

Verify your Helm installation:

1
2
3
$ helm version

version.BuildInfo{Version:"v3.16.4", GitCommit:"...", GitTreeState:"clean", GoVersion:"go1.23.4"}

What is a Helm Chart Repository?

A Helm chart repository is simply an HTTP server that serves two things:

  1. index.yaml - A metadata file that lists all available charts, their versions, and download URLs.
  2. Packaged chart archives (.tgz files) - The actual chart bundles that Helm downloads when you run helm install.

When you run helm repo add, Helm fetches the index.yaml from the repository URL. When you install a chart, Helm downloads the corresponding .tgz file referenced in that index.

GitHub Pages is ideal for this because it serves static files over HTTPS from a Git branch, which is exactly what a Helm repository requires.

Step 1 - Create a GitHub Repository

Start by creating a new repository on GitHub. Head over to GitHub’s new repository page and create a repository. I will name mine helm-charts, but you can choose any name that suits your project.

Make sure to:

  • Initialize the repository with a README (optional but recommended).
  • Set the repository visibility to Public if you want the charts to be publicly accessible. Private repositories can also use GitHub Pages but require a GitHub Pro or Enterprise plan.

Clone the repository to your local machine:

1
2
git clone [email protected]:etowett/helm-charts.git
cd helm-charts

Step 2 - Create a Helm Chart

With the repository cloned, create a charts/ directory to hold your chart source files. This directory structure keeps the repository organized, especially when you have multiple charts.

1
mkdir -p charts

Now create a new Helm chart. For this guide, we will use a chart named app:

1
helm create charts/app

You should see output similar to:

1
Creating charts/app

This generates the default Helm chart scaffold with the following structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
charts/app/
  Chart.yaml          # Chart metadata (name, version, description)
  values.yaml         # Default configuration values
  templates/          # Kubernetes manifest templates
    deployment.yaml
    service.yaml
    ingress.yaml
    hpa.yaml
    serviceaccount.yaml
    _helpers.tpl
    NOTES.txt
    tests/
      test-connection.yaml

Understanding Key Chart Files

  • Chart.yaml - Defines the chart name, version, appVersion, and other metadata. The version field is critical because chart-releaser uses it to detect new releases.
  • values.yaml - Contains default values that users can override during installation. This is where you define image tags, replica counts, resource limits, and other configurable parameters.
  • templates/ - Contains Go-templated Kubernetes manifests that Helm renders using the values from values.yaml.

You can customize the generated chart to match your application’s needs. For example, update charts/app/Chart.yaml:

1
2
3
4
5
6
apiVersion: v2
name: app
description: A Helm chart for deploying my application to Kubernetes
type: application
version: 0.1.0
appVersion: "1.0.0"

Once you are happy with the chart, commit and push:

1
2
3
git add .
git commit -m "Add initial app helm chart"
git push origin main

Step 3 - Set Up GitHub Pages

GitHub Pages will serve the index.yaml and chart archives from a dedicated branch. We need to create an orphan gh-pages branch that is separate from the main branch.

Create the gh-pages branch:

1
2
3
4
git checkout --orphan gh-pages
git rm -rf .
git commit -m "Initialize gh-pages branch" --allow-empty
git push origin gh-pages

Switch back to the main branch:

1
git checkout main

Next, enable GitHub Pages in your repository settings:

  1. Navigate to your repository on GitHub.
  2. Go to Settings > Pages.
  3. Under Source, select the gh-pages branch and set the folder to / (root).
  4. Click Save.

GitHub will now serve the contents of the gh-pages branch at https://<your-username>.github.io/<repo-name>/. For my repository, that would be https://etowett.github.io/helm-charts/.

Step 4 - Set Up the Release Workflow with GitHub Actions

The core of the automation is the Helm Chart Releaser Action. This GitHub Action detects new chart versions, packages them, creates GitHub releases with the .tgz archives, and updates the index.yaml on the gh-pages branch.

Create the workflow file at .github/workflows/release.yml:

 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
---
name: Release Helm Charts

on:
  push:
    branches:
      - main

jobs:
  release:
    permissions:
      contents: write
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@v4
      with:
        fetch-depth: 0

    - name: Configure Git
      run: |
        git config user.name "$GITHUB_ACTOR"
        git config user.email "[email protected]"

    - name: Install Helm
      uses: azure/setup-helm@v4

    - name: Run chart-releaser
      uses: helm/[email protected]
      with:
        charts_dir: charts
        pages_branch: gh-pages
      env:
        CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

How the Release Workflow Works

Here is what happens when you push a change to the main branch:

  1. Checkout - The action checks out the full Git history (fetch-depth: 0) so chart-releaser can compare chart versions across commits.
  2. Configure Git - Sets the Git user for commits to the gh-pages branch.
  3. Install Helm - Installs the Helm CLI on the runner.
  4. Chart Releaser - This is the key step. The action:
    • Scans the charts/ directory for charts.
    • Compares each chart’s version in Chart.yaml against existing GitHub releases.
    • If a new version is detected, it packages the chart into a .tgz file.
    • Creates a GitHub release tagged with the chart name and version (e.g., app-0.1.0).
    • Attaches the .tgz file to the release.
    • Updates (or creates) the index.yaml on the gh-pages branch with metadata pointing to the new release.

The permissions: contents: write block is required because the action needs to create releases and push to the gh-pages branch. The GITHUB_TOKEN is automatically provided by GitHub Actions, so no manual secret configuration is needed.

Commit and push this workflow:

1
2
3
git add .github/workflows/release.yml
git commit -m "Add Helm chart release workflow"
git push origin main

After pushing, navigate to the Actions tab in your repository to watch the workflow run. Once complete, check the Releases section to confirm that a release was created for your chart.

Step 5 - Add a CI Workflow for Linting and Testing

A release pipeline is only as good as the quality checks that gate it. Let’s add a CI workflow that runs on pull requests to lint, validate, and test charts before they get merged.

Create the Chart Testing Configuration

First, create a configuration file for the chart-testing tool at .github/ct.yaml:

1
2
3
4
chart-dirs:
  - charts
target-branch: main
helm-extra-args: --timeout 600s

Create the Kubeval Validation Script

Kubeval validates Kubernetes manifests against the OpenAPI schemas for specific Kubernetes versions. Create the script at .github/kubeval.sh:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#!/bin/bash
set -euo pipefail

CHART_DIRS="$(git diff --find-renames --name-only "$(git rev-parse --abbrev-ref HEAD)" remotes/origin/main -- charts | grep '[Cc]hart.yaml' | sed -e 's#/[Cc]hart.yaml##g')"
KUBEVAL_VERSION="v0.16.1"
SCHEMA_LOCATION="https://raw.githubusercontent.com/instrumenta/kubernetes-json-schema/master/"

# Install kubeval
curl --silent --show-error --fail --location --output /tmp/kubeval.tar.gz \
    "https://github.com/instrumenta/kubeval/releases/download/${KUBEVAL_VERSION}/kubeval-linux-amd64.tar.gz"
tar -xf /tmp/kubeval.tar.gz kubeval

# Validate each changed chart
for CHART_DIR in ${CHART_DIRS}; do
  helm template "${CHART_DIR}" | ./kubeval --strict --ignore-missing-schemas \
    --kubernetes-version "${KUBERNETES_VERSION#v}" \
    --schema-location "${SCHEMA_LOCATION}"
done

Make the script executable:

1
chmod +x .github/kubeval.sh

Create the CI Workflow

Create the workflow file at .github/workflows/ci.yaml:

 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
---
name: Helm Lint and Test Charts

on:
  pull_request:
    paths:
      - "charts/**"

jobs:
  lint-chart:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Helm
        uses: azure/setup-helm@v4

      - name: Set up chart-testing
        uses: helm/[email protected]

      - name: Run chart-testing (lint)
        run: ct lint --config .github/ct.yaml

  kubeval-chart:
    runs-on: ubuntu-latest
    needs:
      - lint-chart
    strategy:
      matrix:
        k8s:
          - v1.28.0
          - v1.29.0
          - v1.30.0
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Helm
        uses: azure/setup-helm@v4

      - name: Run kubeval
        env:
          KUBERNETES_VERSION: ${{ matrix.k8s }}
        run: .github/kubeval.sh

  install-chart:
    runs-on: ubuntu-latest
    needs:
      - lint-chart
      - kubeval-chart
    strategy:
      matrix:
        k8s:
          - v1.28.13
          - v1.29.8
          - v1.30.4
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Helm
        uses: azure/setup-helm@v4

      - name: Set up chart-testing
        uses: helm/[email protected]

      - name: Create kind ${{ matrix.k8s }} cluster
        uses: helm/[email protected]
        with:
          node_image: kindest/node:${{ matrix.k8s }}

      - name: Run chart-testing (install)
        run: ct install --config .github/ct.yaml

What the CI Pipeline Does

The CI pipeline runs three jobs sequentially:

1. Lint (lint-chart) - Uses chart-testing to:

  • Validate Chart.yaml against the Helm schema.
  • Lint Chart.yaml and values.yaml for YAML formatting issues.
  • Check that chart versions have been incremented on changed charts.
  • Verify maintainer information is present.

2. Kubeval (kubeval-chart) - Renders the chart templates and validates the resulting Kubernetes manifests against the OpenAPI schemas for multiple Kubernetes versions (1.28, 1.29, 1.30). This catches issues like using API versions that have been removed in newer Kubernetes releases.

3. Install Test (install-chart) - Spins up a KIND (Kubernetes in Docker) cluster for each Kubernetes version in the matrix and performs an actual helm install followed by helm test. This verifies that the chart not only renders correctly but also deploys and runs successfully on a real cluster.

Commit and push these CI files:

1
2
3
git add .github/ct.yaml .github/kubeval.sh .github/workflows/ci.yaml
git commit -m "Add Helm chart CI workflow for linting and testing"
git push origin main

Step 6 - Use the Helm Chart Repository

With the release workflow complete and charts published, you can now add your repository to any Helm client and install charts from it.

Add the Repository

1
2
helm repo add mycharts https://etowett.github.io/helm-charts/
helm repo update

Replace etowett with your GitHub username and helm-charts with your repository name.

Search for Available Charts

1
2
3
4
$ helm search repo mycharts

NAME           CHART VERSION   APP VERSION   DESCRIPTION
mycharts/app   0.1.0           1.0.0         A Helm chart for deploying my application to Kubernetes

Install a Chart

To install the app chart into your Kubernetes cluster:

1
helm upgrade --install app mycharts/app

You can override default values during installation:

1
2
3
4
helm upgrade --install app mycharts/app \
  --set image.repository=myregistry/myapp \
  --set image.tag=v1.2.3 \
  --set replicaCount=3

Or use a custom values file:

1
helm upgrade --install app mycharts/app -f custom-values.yaml

Verify the Deployment

After installation, verify that the resources were created:

1
2
kubectl get pods -l app.kubernetes.io/name=app
kubectl get svc -l app.kubernetes.io/name=app

Releasing a New Chart Version

To release a new version of your chart, the process is straightforward:

  1. Make your changes to the chart templates, values, or configuration.
  2. Bump the version field in charts/app/Chart.yaml. Chart-releaser only creates a release when it detects a version that does not already have a corresponding GitHub release.
  3. Commit and push to main (or open a PR that gets merged to main).
  4. The GitHub Actions release workflow will automatically package, release, and update the repository index.

For example, to release version 0.2.0:

1
2
3
4
# Edit charts/app/Chart.yaml and set version: 0.2.0
git add charts/app/
git commit -m "Bump app chart to 0.2.0"
git push origin main

After the workflow completes, update your local repository cache to see the new version:

1
2
helm repo update
helm search repo mycharts --versions

Troubleshooting Common Issues

Here are some common issues you might encounter and how to resolve them:

Chart-releaser does not detect changes - Make sure you incremented the version field in Chart.yaml. Chart-releaser compares versions against existing GitHub releases, not file diffs.

GitHub Pages returns 404 - Verify that the gh-pages branch exists and that GitHub Pages is configured to serve from it in the repository settings. It can take a few minutes for Pages to become active after initial setup.

helm repo add fails with a 404 - Ensure the index.yaml file exists on the gh-pages branch. The release workflow creates this file on the first successful release. If you have not pushed a chart version yet, the index will not exist.

CI linting fails on unchanged charts - The chart-testing tool only lints charts that have changed compared to the target branch. If no charts were modified in the pull request, the lint step will skip with a success status.

KIND cluster fails to start in CI - This is usually caused by resource constraints on the GitHub Actions runner. Make sure you are not running too many parallel jobs that each spin up a KIND cluster.

Conclusion

In this guide, we set up a complete Helm chart repository hosted on GitHub Pages with a fully automated CI/CD pipeline using GitHub Actions. The pipeline handles:

  • Linting and validation on pull requests to catch issues early.
  • Kubeval validation against multiple Kubernetes versions for compatibility assurance.
  • Install testing on real KIND clusters to verify charts deploy correctly.
  • Automated releases on push to main, including chart packaging, GitHub release creation, and repository index updates.

This approach gives you a free, version-controlled, and fully automated Helm chart distribution system that scales from personal projects to team-wide use. Every chart change is validated before merge and automatically published after, with no manual packaging or index management required.

comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy