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:
| |
What is a Helm Chart Repository?
A Helm chart repository is simply an HTTP server that serves two things:
index.yaml- A metadata file that lists all available charts, their versions, and download URLs.- Packaged chart archives (
.tgzfiles) - The actual chart bundles that Helm downloads when you runhelm 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:
| |
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.
| |
Now create a new Helm chart. For this guide, we will use a chart named app:
| |
You should see output similar to:
| |
This generates the default Helm chart scaffold with the following structure:
| |
Understanding Key Chart Files
Chart.yaml- Defines the chart name, version, appVersion, and other metadata. Theversionfield 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 fromvalues.yaml.
You can customize the generated chart to match your application’s needs. For example, update charts/app/Chart.yaml:
| |
Once you are happy with the chart, commit and push:
| |
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:
| |
Switch back to the main branch:
| |
Next, enable GitHub Pages in your repository settings:
- Navigate to your repository on GitHub.
- Go to Settings > Pages.
- Under Source, select the
gh-pagesbranch and set the folder to/ (root). - 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:
| |
How the Release Workflow Works
Here is what happens when you push a change to the main branch:
- Checkout - The action checks out the full Git history (
fetch-depth: 0) so chart-releaser can compare chart versions across commits. - Configure Git - Sets the Git user for commits to the
gh-pagesbranch. - Install Helm - Installs the Helm CLI on the runner.
- Chart Releaser - This is the key step. The action:
- Scans the
charts/directory for charts. - Compares each chart’s
versioninChart.yamlagainst existing GitHub releases. - If a new version is detected, it packages the chart into a
.tgzfile. - Creates a GitHub release tagged with the chart name and version (e.g.,
app-0.1.0). - Attaches the
.tgzfile to the release. - Updates (or creates) the
index.yamlon thegh-pagesbranch with metadata pointing to the new release.
- Scans the
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:
| |
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:
| |
Create the Kubeval Validation Script
Kubeval validates Kubernetes manifests against the OpenAPI schemas for specific Kubernetes versions. Create the script at .github/kubeval.sh:
| |
Make the script executable:
| |
Create the CI Workflow
Create the workflow file at .github/workflows/ci.yaml:
| |
What the CI Pipeline Does
The CI pipeline runs three jobs sequentially:
1. Lint (lint-chart) - Uses chart-testing to:
- Validate
Chart.yamlagainst the Helm schema. - Lint
Chart.yamlandvalues.yamlfor 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:
| |
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
| |
Replace etowett with your GitHub username and helm-charts with your repository name.
Search for Available Charts
| |
Install a Chart
To install the app chart into your Kubernetes cluster:
| |
You can override default values during installation:
| |
Or use a custom values file:
| |
Verify the Deployment
After installation, verify that the resources were created:
| |
Releasing a New Chart Version
To release a new version of your chart, the process is straightforward:
- Make your changes to the chart templates, values, or configuration.
- Bump the
versionfield incharts/app/Chart.yaml. Chart-releaser only creates a release when it detects a version that does not already have a corresponding GitHub release. - Commit and push to
main(or open a PR that gets merged tomain). - The GitHub Actions release workflow will automatically package, release, and update the repository index.
For example, to release version 0.2.0:
| |
After the workflow completes, update your local repository cache to see the new version:
| |
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.