Hosting a Helm Chart Repository on GitHub with chart-releaser
GitHub Pages plus the helm/chart-releaser-action is the de facto way to turn a GitHub repository into a Helm chart repository. No extra hosting, no separate registry — just a gh-pages branch and a workflow file.
This post walks through the layout I use in practice: one repository for the application, a separate one for the chart.
Why a Dedicated Chart Repository?
It is tempting to keep the Helm chart inside the application repository, in a charts/ or deploy/ folder. It works, but in practice the two artifacts have different lifecycles:
- The application is versioned by what its source code does. A patch release fixes a bug in the binary.
- The chart is versioned by what gets deployed. A patch release might tweak a probe, a label, or a default value — without changing a single line of application code.
Mixing both in the same repository means every chart-only change ends up in the application’s git history, and every application release pressures you to bump the chart even when nothing relevant changed. Splitting them keeps each history readable and lets the chart evolve independently.
So for one application myApplication, I create two GitHub repositories:
myApplication— the source codehelm-myApplication— the Helm chart
Bootstrapping the Chart Repository
Here is the sequence I follow when I create the chart repository.
1. Create the repository
Create helm-myApplication on GitHub. Initialize it however you want — even an empty repo is fine.
2. Configure GitHub Pages
In the repository Settings → Pages, set the source to the gh-pages branch (root). Once Pages is enabled, your chart repo URL will be:
https://<your-account>.github.io/helm-myApplication
This URL is what users will pass to helm repo add. You can also surface it in the repository sidebar via Settings → General → Website, so visitors land on the chart index directly.
3. Create an orphan gh-pages branch
The chart-releaser action publishes the packaged charts and the index.yaml to a branch — gh-pages by default. That branch must exist and should not share history with main:
git checkout --orphan gh-pages
git rm -rf .
Add a minimal README.md to the branch so visitors landing on the GitHub repository’s gh-pages view get a pointer back to the application:
# helm-myApplication
Helm chart for [myApplication](https://github.com/<your-account>/myApplication).
Add the repository:
```bash
helm repo add myapp https://<your-account>.github.io/helm-myApplication
helm repo update
helm search repo myapp
```
Commit and push:
git add README.md
git commit -m "chore: initialize gh-pages branch"
git push -u origin gh-pages
4. Switch back to main and add the chart
git checkout main
mkdir -p charts
helm create charts/myApplication
Edit charts/myApplication/Chart.yaml to set the version, app version, description, and maintainers. Commit and push.
The repository should now look like:
helm-myApplication/
├── .github/
│ └── workflows/
│ └── release.yml # added next
├── charts/
│ └── myApplication/
│ ├── Chart.yaml
│ ├── values.yaml
│ └── templates/
└── README.md
Wiring chart-releaser
Add .github/workflows/release.yml:
name: Release Charts
on:
push:
branches:
- main
permissions:
contents: write # required to push to gh-pages and create releases
pages: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # chart-releaser needs full history
- 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@v4
- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.6.0
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
What this does on every push to main:
- Detects which charts under
charts/had theirversionbumped inChart.yaml. - Packages them into
.tgzarchives. - Creates a GitHub Release per chart version, with the
.tgzattached as a release asset. - Updates
index.yamlon thegh-pagesbranch to point at those release assets.
The crucial detail is that the chart tarballs live as GitHub Release assets, not on the gh-pages branch itself. Only index.yaml and an optional README.md are committed to gh-pages. This keeps the branch tiny no matter how many chart versions you publish.
⚠️ The action only triggers a new release when
Chart.yaml’sversion:field changes. Forgetting to bump it is the most common reason “the workflow ran but no release appeared”.
Consuming the Chart
Once the first release has been published:
helm repo add myapp https://<your-account>.github.io/helm-myApplication
helm repo update
helm install myapp myapp/myApplication
You can verify the index manually:
curl https://<your-account>.github.io/helm-myApplication/index.yaml
It should list every released chart version with a urls: entry pointing to the GitHub Release asset.
Keeping the Pipeline Healthy with Dependabot
The chart repo has very few moving parts, but the few it has — actions/checkout, azure/setup-helm, helm/chart-releaser-action — drift over time. Add .github/dependabot.yml so Dependabot opens PRs whenever a new version is published:
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
For a repository whose only purpose is releasing charts, this is the cheapest possible way to stay current.
Recap
The full bootstrapping checklist for one application:
- Create
helm-myApplicationon GitHub. - Enable GitHub Pages (
gh-pagesbranch, root). - Create the orphan
gh-pagesbranch with aREADME.mdpointing to the application. - On
main, add the chart undercharts/. - Add
.github/workflows/release.ymlcallinghelm/chart-releaser-action. - Add
.github/dependabot.ymlfor thegithub-actionsecosystem. - Bump
Chart.yaml’sversion:and push tomain— the action does the rest.
Splitting the application from its chart costs one extra repository, but it pays back every time you release: the application history stays focused on code, and chart changes ship through a workflow that does one thing well.