Helm with GitHub Actions

A comprehensive guide to Helm with GitHub Actions covering CI/CD pipelines, linting, testing, publishing charts, and best practices for automated Helm workflows.

GitHub Actions CI/CD Pipelines Chart Publishing
Why GitHub Actions for Helm?

GitHub Actions provides a powerful, integrated CI/CD platform for automating Helm chart workflows. It offers:

  • Native GitHub Integration: Deep integration with GitHub repositories
  • Large Marketplace: Thousands of reusable actions
  • Matrix Builds: Test across multiple Kubernetes versions
  • Secrets Management: Secure handling of credentials
  • Artifact Storage: Store and share chart packages
  • Cost-Effective: Free for public repositories
Key Concept: GitHub Actions enables you to automate the entire Helm chart lifecycle: linting, testing, packaging, signing, and publishing—all triggered by Git events.
Basic Helm Workflow
# .github/workflows/helm.yml name: Helm Chart CI on: push: branches: [ main ] paths: - 'charts/**' pull_request: branches: [ main ] paths: - 'charts/**' jobs: lint: name: Lint Helm Charts runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Install Helm uses: azure/setup-helm@v3 with: version: 'v3.14.0' - name: Lint charts run: | for chart in charts/*; do if [ -d "$chart" ]; then echo "Linting $chart" helm lint "$chart" fi done - name: Validate chart templates run: | for chart in charts/*; do if [ -d "$chart" ]; then echo "Validating $chart" helm template test-release "$chart" > /dev/null fi done test: name: Test Helm Charts runs-on: ubuntu-latest needs: lint steps: - name: Checkout code uses: actions/checkout@v4 - name: Install Helm uses: azure/setup-helm@v3 with: version: 'v3.14.0' - name: Install helm-unittest run: | helm plugin install https://github.com/helm-unittest/helm-unittest - name: Run unit tests run: | for chart in charts/*; do if [ -d "$chart" ]; then echo "Testing $chart" helm unittest "$chart" fi done - name: Validate with kubeconform run: | wget https://github.com/yannh/kubeconform/releases/latest/download/kubeconform-linux-amd64.tar.gz tar xf kubeconform-linux-amd64.tar.gz for chart in charts/*; do if [ -d "$chart" ]; then echo "Validating $chart" helm template test-release "$chart" | ./kubeconform -strict fi done package: name: Package Helm Charts runs-on: ubuntu-latest needs: test steps: - name: Checkout code uses: actions/checkout@v4 - name: Install Helm uses: azure/setup-helm@v3 with: version: 'v3.14.0' - name: Package charts run: | mkdir -p dist for chart in charts/*; do if [ -d "$chart" ]; then echo "Packaging $chart" helm package "$chart" --destination dist fi done - name: Upload artifacts uses: actions/upload-artifact@v3 with: name: helm-charts path: dist/*.tgz
Basic Workflow Benefits:
  • Automated linting on every push
  • Unit testing with helm-unittest
  • Validation with kubeconform
  • Chart packaging for distribution
  • Artifact storage for later use
Publishing Workflows

Publish to GitHub Pages (Traditional Helm Repo)

# .github/workflows/publish-gh-pages.yml name: Publish Helm Charts to GitHub Pages on: push: branches: [ main ] paths: - 'charts/**' jobs: publish: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Install Helm uses: azure/setup-helm@v3 with: version: 'v3.14.0' - name: Configure Git run: | git config user.name "$GITHUB_ACTOR" git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - name: Run chart-releaser uses: helm/chart-releaser-action@v1.5.0 with: charts_dir: charts config: cr.yaml env: CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - name: Verify published charts run: | helm repo add my-repo https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }} helm repo update helm search repo my-repo

Publish to OCI Registry (GHCR)

# .github/workflows/publish-oci.yml name: Publish Helm Charts to OCI on: push: tags: - 'v*' jobs: publish: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout code uses: actions/checkout@v4 - name: Install Helm uses: azure/setup-helm@v3 with: version: 'v3.14.0' - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | \ helm registry login ghcr.io -u ${{ github.actor }} --password-stdin - name: Package and Push Charts run: | for chart in charts/*; do if [ -d "$chart" ]; then echo "Publishing $chart" helm package "$chart" --destination dist helm push dist/$(basename $chart)-*.tgz oci://ghcr.io/${{ github.repository }}/charts fi done - name: Verify published charts run: | helm pull oci://ghcr.io/${{ github.repository }}/charts/$(basename $chart) --version $(helm show chart charts/$chart | grep version | awk '{print $2}') # Publish to AWS ECR - name: Login to AWS ECR run: | aws ecr get-login-password --region us-east-1 | \ helm registry login --username AWS --password-stdin ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com - name: Push to ECR run: | helm push dist/*.tgz oci://${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/helm # Publish to Azure ACR - name: Login to Azure ACR uses: azure/docker-login@v1 with: login-server: ${{ secrets.ACR_REGISTRY }} username: ${{ secrets.ACR_USERNAME }} password: ${{ secrets.ACR_PASSWORD }} - name: Push to ACR run: | helm registry login ${{ secrets.ACR_REGISTRY }} -u ${{ secrets.ACR_USERNAME }} -p ${{ secrets.ACR_PASSWORD }} helm push dist/*.tgz oci://${{ secrets.ACR_REGISTRY }}/helm

GitHub Pages

Traditional Helm repository hosted on GitHub Pages. Free and simple.

GHCR

GitHub Container Registry. OCI-compliant, integrated with GitHub.

AWS ECR

Amazon Elastic Container Registry. Enterprise-grade with IAM.

Azure ACR

Azure Container Registry. Integrated with Azure AD and DevOps.
Chart Testing Workflow
# .github/workflows/test-charts.yml name: Test Helm Charts on: pull_request: paths: - 'charts/**' jobs: test: name: Test Charts runs-on: ubuntu-latest strategy: matrix: k8s-version: [v1.27.0, v1.28.0, v1.29.0] steps: - name: Checkout code uses: actions/checkout@v4 - name: Install Helm uses: azure/setup-helm@v3 with: version: 'v3.14.0' - name: Install helm-unittest run: helm plugin install https://github.com/helm-unittest/helm-unittest - name: Run unit tests run: helm unittest charts/* - name: Create kind cluster uses: helm/kind-action@v1 with: node_image: kindest/node:${{ matrix.k8s-version }} - name: Install chart run: | for chart in charts/*; do if [ -d "$chart" ]; then echo "Installing $chart" helm install test-release "$chart" --wait --timeout 5m fi done - name: Run helm test run: | for release in $(helm list -q); do echo "Testing $release" helm test "$release" --timeout 5m done - name: Check pod status run: | kubectl get pods --all-namespaces kubectl get svc --all-namespaces kubectl get deployments --all-namespaces - name: Cleanup run: | for release in $(helm list -q); do helm uninstall "$release" done kind delete cluster # Matrix testing across Kubernetes versions strategy: matrix: k8s-version: [v1.27.0, v1.28.0, v1.29.0] chart: [nginx, postgresql, redis] # Use chart-testing tool - name: Run chart-testing uses: helm/chart-testing-action@v2.4.0 - name: List changed charts id: list-changed run: | changed=$(ct list-changed --config ct.yaml) if [[ -n "$changed" ]]; then echo "changed=true" >> $GITHUB_OUTPUT fi - name: Lint charts if: steps.list-changed.outputs.changed == 'true' run: ct lint --config ct.yaml - name: Create kind cluster if: steps.list-changed.outputs.changed == 'true' uses: helm/kind-action@v1 - name: Install and test charts if: steps.list-changed.outputs.changed == 'true' run: ct install --config ct.yaml
chart-testing (ct) Tool:
  • Automatically detects changed charts
  • Runs linting and validation
  • Installs charts in a kind cluster
  • Runs helm tests
  • Supports matrix testing across K8s versions
Advanced Workflows

Chart Signing Workflow

# .github/workflows/sign-charts.yml name: Sign Helm Charts on: push: tags: - 'v*' jobs: sign: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Install Helm uses: azure/setup-helm@v3 with: version: 'v3.14.0' - name: Import GPG Key run: | echo "${{ secrets.GPG_PRIVATE_KEY }}" | \ gpg --batch --import echo "${{ secrets.GPG_PASSPHRASE }}" | \ gpg --batch --passphrase-fd 0 --pinentry-mode loopback --import env: GPG_TTY: /dev/tty - name: Package and Sign run: | for chart in charts/*; do if [ -d "$chart" ]; then helm package "$chart" \ --sign \ --key "${{ secrets.GPG_KEY_NAME }}" \ --destination dist fi done - name: Verify Signatures run: | for chart in dist/*.tgz; do echo "Verifying $chart" helm verify "$chart" done - name: Upload Signed Charts uses: actions/upload-artifact@v3 with: name: signed-charts path: | dist/*.tgz dist/*.tgz.prov

Security Scanning Workflow

# .github/workflows/security-scan.yml name: Security Scan Helm Charts on: pull_request: paths: - 'charts/**' jobs: scan: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Install Helm uses: azure/setup-helm@v3 with: version: 'v3.14.0' - name: Install Trivy uses: aquasecurity/setup-trivy@v0.2.0 - name: Scan charts for vulnerabilities run: | for chart in charts/*; do if [ -d "$chart" ]; then echo "Scanning $chart" trivy config "$chart" fi done - name: Validate with kube-score run: | wget https://github.com/zegl/kube-score/releases/latest/download/kube-score_linux_amd64 chmod +x kube-score_linux_amd64 for chart in charts/*; do if [ -d "$chart" ]; then helm template test "$chart" | ./kube-score_linux_amd64 score - fi done - name: Check for secrets in charts uses: gitleaks/gitleaks-action@v2 with: config-path: .gitleaks.toml - name: Upload scan results uses: actions/upload-artifact@v3 with: name: security-scan-results path: | trivy-results/ kube-score-results/

Automated Release Workflow

# .github/workflows/release.yml name: Release Helm Charts on: push: tags: - 'v*' jobs: release: runs-on: ubuntu-latest permissions: contents: write packages: write steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install Helm uses: azure/setup-helm@v3 with: version: 'v3.14.0' - name: Configure Git run: | git config user.name "$GITHUB_ACTOR" git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - name: Package Charts run: | mkdir -p dist for chart in charts/*; do if [ -d "$chart" ]; then helm package "$chart" --destination dist fi done - name: Generate Changelog id: changelog run: | echo "## What's Changed" > CHANGELOG.md git log --oneline --no-merges $(git describe --tags --abbrev=0 HEAD^)..HEAD >> CHANGELOG.md - name: Create GitHub Release uses: softprops/action-gh-release@v1 with: files: | dist/*.tgz dist/*.tgz.prov body_path: CHANGELOG.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Publish to OCI run: | echo "${{ secrets.GITHUB_TOKEN }}" | \ helm registry login ghcr.io -u ${{ github.actor }} --password-stdin for chart in dist/*.tgz; do helm push "$chart" oci://ghcr.io/${{ github.repository }}/charts done - name: Update Helm Repository run: | helm repo index dist --url https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }} git checkout gh-pages cp dist/index.yaml . git add index.yaml git commit -m "Update Helm repository index" git push origin gh-pages
GitHub Actions Best Practices

Use Secrets

Store credentials in GitHub Secrets, never in workflow files.

Pin Action Versions

Use specific action versions (e.g., @v4) instead of @master.

Matrix Testing

Test against multiple Kubernetes versions and chart combinations.

Use Caching

Cache Helm dependencies and Go modules for faster builds.

Path Filters

Trigger workflows only when chart files change.

Validate Charts

Always lint and validate charts before publishing.

Sign Charts

Sign charts with GPG keys for authenticity.

Security Scans

Scan charts for vulnerabilities and secrets.
# Best practices summary # 1. Use secrets for credentials - name: Login to registry run: echo "${{ secrets.REGISTRY_TOKEN }}" | helm registry login ... # 2. Pin action versions uses: azure/setup-helm@v3 # Specific version # uses: azure/setup-helm@master # Avoid this # 3. Use path filters on: push: paths: - 'charts/**' - '.github/workflows/helm.yml' # 4. Use caching - name: Cache Helm dependencies uses: actions/cache@v3 with: path: ~/.cache/helm key: ${{ runner.os }}-helm-${{ hashFiles('**/Chart.lock') }} # 5. Matrix testing strategy: matrix: k8s-version: [v1.27.0, v1.28.0, v1.29.0] # 6. Use concurrency control concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true # 7. Use environment protection environment: name: production url: https://myapp.example.com # 8. Require approvals for production environment: name: production # Configure protection rules in GitHub settings
Frequently Asked Questions
How do I trigger a Helm workflow only when charts change?
Use path filters in the on section: paths: - 'charts/**'. This ensures the workflow only runs when chart files are modified.
How do I publish Helm charts to GitHub Pages?
Use the helm/chart-releaser-action action. It packages charts, creates GitHub releases, and updates the gh-pages branch with the repository index.
How do I publish charts to OCI registries?
Use helm registry login and helm push commands. For GHCR, use GITHUB_TOKEN; for AWS ECR, use AWS credentials; for Azure ACR, use Azure credentials.
How do I test Helm charts in GitHub Actions?
Use helm-unittest for unit tests, kind-action for integration testing, and chart-testing (ct) for comprehensive testing. Validate with kubeconform and kube-score.
How do I sign Helm charts in GitHub Actions?
Import your GPG private key from GitHub Secrets, then use helm package --sign --key. Store the key and passphrase as secrets.
How do I scan Helm charts for vulnerabilities?
Use Trivy, kube-score, and gitleaks. Run security scans in your CI/CD pipeline before publishing charts.
What is chart-testing (ct)?
chart-testing is a tool that detects changed charts, runs linting, installs charts in a kind cluster, and runs helm tests. It's ideal for PR validation.
How do I use matrix builds for Helm charts?
Define a matrix in your workflow with different Kubernetes versions or chart names. Use strategy.matrix to run tests in parallel across multiple configurations.
Previous: Helm with ArgoCD Next: Helm with Jenkins

GitHub Actions provides a powerful platform for automating Helm chart workflows. Implement these CI/CD pipelines to ensure quality, security, and reliability in your Helm deployments.