Helm Chart Repositories - Distribution and Management

Helm repositories enable teams to share, version, and distribute charts efficiently across organizations. Rather than passing around chart directories or packages manually, repositories provide centralized, versioned storage with standardized discovery mechanisms. Understanding how to set up, manage, and consume repositories is essential for scaling Helm usage beyond individual developers to enterprise-wide platform engineering.

Understanding Helm Repositories

A Helm repository is essentially an HTTP server hosting packaged charts and an index file describing available charts and versions:

graph TB A[Helm Repository] --> B[Chart Packages
.tgz files] A --> C[index.yaml
Chart metadata] B --> B1[myapp-1.0.0.tgz] B --> B2[myapp-1.1.0.tgz] B --> B3[database-2.0.0.tgz] C --> C1[Chart names] C --> C2[Versions] C --> C3[URLs] C --> C4[Dependencies]

Repository Structure:

repository/ ├── index.yaml # Chart index ├── myapp-1.0.0.tgz # Chart package v1.0.0 ├── myapp-1.1.0.tgz # Chart package v1.1.0 ├── myapp-2.0.0.tgz # Chart package v2.0.0 ├── database-1.0.0.tgz # Different chart └── webserver-3.2.1.tgz # Another chart

index.yaml Structure:

apiVersion: v1 entries: myapp: - name: myapp version: 2.0.0 appVersion: "2.1.0" description: My application Helm chart created: 2023-10-15T10:30:00.000Z digest: sha256:1234567890abcdef... urls: - https://charts.company.com/myapp-2.0.0.tgz keywords: - api - web maintainers: - name: Platform Team email: platform@company.com - name: myapp version: 1.1.0 appVersion: "1.5.2" description: My application Helm chart created: 2023-09-01T14:20:00.000Z digest: sha256:abcdef1234567890... urls: - https://charts.company.com/myapp-1.1.0.tgz database: - name: database version: 1.0.0 appVersion: "14.0" description: PostgreSQL database chart created: 2023-08-20T09:00:00.000Z digest: sha256:fedcba0987654321... urls: - https://charts.company.com/database-1.0.0.tgz generated: 2023-10-15T10:30:00.000Z

Working with Public Repositories

Helm's ecosystem includes numerous public repositories hosting community charts:

Adding Popular Repositories:

# Bitnami - comprehensive collection of application charts helm repo add bitnami https://charts.bitnami.com/bitnami # Prometheus Community - monitoring charts helm repo add prometheus-community https://prometheus-community.github.io/helm-charts # Jetstack - cert-manager and related charts helm repo add jetstack https://charts.jetstack.io # Ingress NGINX - ingress controller helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx # Artifact Hub - search across multiple repositories # Visit https://artifacthub.io for discovery

Managing Repositories:

# List configured repositories helm repo list # Update repository indexes helm repo update # Remove a repository helm repo remove bitnami # Search for charts in repositories helm search repo nginx # Search with version constraints helm search repo nginx --version '>1.0.0' # Show all versions helm search repo nginx --versions

Installing from Repositories:

# Install latest version helm install my-nginx bitnami/nginx # Install specific version helm install my-nginx bitnami/nginx --version 13.2.0 # Show chart information before installing helm show chart bitnami/nginx helm show values bitnami/nginx helm show readme bitnami/nginx # Pull chart without installing helm pull bitnami/nginx helm pull bitnami/nginx --version 13.2.0 --untar

Creating a Chart Repository

Setting up a private repository enables distribution of internal charts:

Option 1: Simple HTTP Server

The simplest repository is a directory served by any HTTP server:

Step 1: Package Charts

# Package individual charts helm package ./myapp helm package ./database helm package ./webserver # Output: myapp-1.0.0.tgz, database-1.0.0.tgz, webserver-1.0.0.tgz

Step 2: Generate Index

# Create repository directory mkdir chart-repo mv *.tgz chart-repo/ # Generate index.yaml helm repo index chart-repo/ --url https://charts.company.com # Verify index cat chart-repo/index.yaml

Step 3: Serve with NGINX

# nginx.conf server { listen 80; server_name charts.company.com; root /var/www/charts; location / { autoindex on; autoindex_exact_size off; autoindex_localtime on; } location ~ \.tgz$ { add_header Content-Type application/gzip; add_header Cache-Control "public, max-age=31536000"; } location /index.yaml { add_header Content-Type text/yaml; add_header Cache-Control "no-cache"; } # Enable CORS for browser-based tools add_header Access-Control-Allow-Origin *; }

Kubernetes Deployment:

apiVersion: v1 kind: ConfigMap metadata: name: nginx-config data: nginx.conf: | # nginx configuration as above --- apiVersion: apps/v1 kind: Deployment metadata: name: chart-repository spec: replicas: 2 selector: matchLabels: app: chart-repository template: metadata: labels: app: chart-repository spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80 volumeMounts: - name: charts mountPath: /var/www/charts - name: config mountPath: /etc/nginx/conf.d volumes: - name: charts persistentVolumeClaim: claimName: chart-repo-pvc - name: config configMap: name: nginx-config --- apiVersion: v1 kind: Service metadata: name: chart-repository spec: type: LoadBalancer ports: - port: 80 targetPort: 80 selector: app: chart-repository

Option 2: GitHub Pages

GitHub Pages provides free hosting for Helm repositories:

Repository Setup:

# Create GitHub repository (e.g., company/helm-charts) # Enable GitHub Pages from Settings # Clone repository git clone https://github.com/company/helm-charts.git cd helm-charts # Create charts directory mkdir charts

Adding Charts:

# Copy chart source cp -r ~/myapp ./charts/ # Package chart to docs directory mkdir -p docs helm package charts/myapp -d docs/ # Generate/update index helm repo index docs/ --url https://company.github.io/helm-charts # Commit and push git add . git commit -m "Add myapp chart v1.0.0" git push origin main

Automated Publishing with GitHub Actions:

# .github/workflows/release.yml name: Release Charts on: push: branches: - main paths: - 'charts/**' jobs: release: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 with: fetch-depth: 0 - 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@v3 with: version: v3.12.0 - name: Package charts run: | mkdir -p .cr-release-packages for chart in charts/*; do if [ -d "$chart" ]; then helm package "$chart" -d .cr-release-packages fi done - name: Generate index run: | helm repo index .cr-release-packages \ --url https://company.github.io/helm-charts \ --merge docs/index.yaml - name: Update docs run: | cp .cr-release-packages/*.tgz docs/ cp .cr-release-packages/index.yaml docs/ git add docs/ git commit -m "Update chart repository" git push

Using GitHub Pages Repository:

# Add repository helm repo add company https://company.github.io/helm-charts # Update and search helm repo update helm search repo company/ # Install chart helm install myapp company/myapp

Option 3: ChartMuseum

ChartMuseum provides a dedicated chart repository server with advanced features:

graph TB A[ChartMuseum] --> B[Multi-Backend
Storage] A --> C[API Features] A --> D[Security] B --> B1[Local filesystem] B --> B2[S3 / GCS] B --> B3[Azure Blob] C --> C1[Chart upload API] C --> C2[Chart deletion] C --> C3[Chart versioning] D --> D1[Basic auth] D --> D2[Bearer token] D --> D3[Chart provenance]

ChartMuseum Deployment:

apiVersion: v1 kind: Secret metadata: name: chartmuseum-secret type: Opaque stringData: BASIC_AUTH_USER: admin BASIC_AUTH_PASS: secretpassword --- apiVersion: apps/v1 kind: Deployment metadata: name: chartmuseum spec: replicas: 2 selector: matchLabels: app: chartmuseum template: metadata: labels: app: chartmuseum spec: containers: - name: chartmuseum image: ghcr.io/helm/chartmuseum:v0.16.0 ports: - containerPort: 8080 env: - name: STORAGE value: local - name: STORAGE_LOCAL_ROOTDIR value: /charts - name: DISABLE_API value: "false" - name: ALLOW_OVERWRITE value: "true" - name: BASIC_AUTH_USER valueFrom: secretKeyRef: name: chartmuseum-secret key: BASIC_AUTH_USER - name: BASIC_AUTH_PASS valueFrom: secretKeyRef: name: chartmuseum-secret key: BASIC_AUTH_PASS volumeMounts: - name: storage mountPath: /charts livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 10 readinessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 5 volumes: - name: storage persistentVolumeClaim: claimName: chartmuseum-pvc --- apiVersion: v1 kind: Service metadata: name: chartmuseum spec: type: LoadBalancer ports: - port: 8080 targetPort: 8080 selector: app: chartmuseum

Using ChartMuseum:

# Add repository with authentication helm repo add company https://charts.company.com \ --username admin \ --password secretpassword # Push chart (requires helm-push plugin) helm plugin install https://github.com/chartmuseum/helm-push helm cm-push ./myapp-1.0.0.tgz company # Or use curl curl -u admin:secretpassword \ --data-binary "@myapp-1.0.0.tgz" \ https://charts.company.com/api/charts # Delete chart version curl -X DELETE -u admin:secretpassword \ https://charts.company.com/api/charts/myapp/1.0.0

ChartMuseum with S3 Backend:

env: - name: STORAGE value: amazon - name: STORAGE_AMAZON_BUCKET value: company-helm-charts - name: STORAGE_AMAZON_PREFIX value: charts - name: STORAGE_AMAZON_REGION value: us-west-2 - name: AWS_ACCESS_KEY_ID valueFrom: secretKeyRef: name: aws-credentials key: access-key-id - name: AWS_SECRET_ACCESS_KEY valueFrom: secretKeyRef: name: aws-credentials key: secret-access-key

Option 4: OCI Registry

Helm 3+ supports storing charts in OCI (container) registries:

graph LR A[OCI Registry] --> B[Docker Hub] A --> C[GitHub Container
Registry] A --> D[Harbor] A --> E[Artifactory] A --> F[ECR / ACR / GCR]

Publishing to OCI Registry:

# Login to registry helm registry login ghcr.io -u username # Package chart helm package ./myapp # Push to OCI registry helm push myapp-1.0.0.tgz oci://ghcr.io/company/charts # No need for index.yaml with OCI

Installing from OCI Registry:

# No repo add needed for OCI helm install myapp oci://ghcr.io/company/charts/myapp --version 1.0.0 # Pull chart helm pull oci://ghcr.io/company/charts/myapp --version 1.0.0 # Show chart helm show chart oci://ghcr.io/company/charts/myapp --version 1.0.0

OCI Registry with GitHub Container Registry:

# .github/workflows/push-chart.yml name: Push Chart to GHCR on: push: tags: - 'v*' jobs: push: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout uses: actions/checkout@v3 - name: Install Helm uses: azure/setup-helm@v3 - name: Login to GHCR run: | echo ${{ secrets.GITHUB_TOKEN }} | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin - name: Package and push chart run: | helm package charts/myapp helm push myapp-*.tgz oci://ghcr.io/${{ github.repository_owner }}/charts

Repository Security

Protect chart repositories from unauthorized access:

HTTPS/TLS:

server { listen 443 ssl http2; server_name charts.company.com; ssl_certificate /etc/nginx/certs/tls.crt; ssl_certificate_key /etc/nginx/certs/tls.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; location / { # Repository root } }

Basic Authentication:

location / { auth_basic "Helm Repository"; auth_basic_user_file /etc/nginx/.htpasswd; # Repository root }

Generate htpasswd:

# Install apache2-utils apt-get install apache2-utils # Create password file htpasswd -c /etc/nginx/.htpasswd admin htpasswd /etc/nginx/.htpasswd developer

Using Authenticated Repository:

# Add repository with credentials helm repo add company https://charts.company.com \ --username admin \ --password secretpassword # Store credentials in pass/keychain for automation # Helm will prompt for credentials if not provided

Certificate-Based Authentication:

server { listen 443 ssl; ssl_client_certificate /etc/nginx/ca.crt; ssl_verify_client on; location / { # Only clients with valid certificates can access } }

Repository Management Best Practices

Versioning Strategy:

graph LR A[Version Updates] --> B[Patch
1.0.0 to 1.0.1] A --> C[Minor
1.0.0 to 1.1.0] A --> D[Major
1.0.0 to 2.0.0] B --> B1[Bug fixes
No breaking changes] C --> C1[New features
Backwards compatible] D --> D1[Breaking changes
Incompatible]

Chart Lifecycle:

# Development - alpha versions myapp-1.0.0-alpha.1.tgz myapp-1.0.0-alpha.2.tgz # Testing - beta versions myapp-1.0.0-beta.1.tgz myapp-1.0.0-beta.2.tgz # Release candidates myapp-1.0.0-rc.1.tgz # Stable release myapp-1.0.0.tgz # Patch updates myapp-1.0.1.tgz myapp-1.0.2.tgz # Feature releases myapp-1.1.0.tgz myapp-1.2.0.tgz # Major version myapp-2.0.0.tgz

Multi-Environment Strategy:

repositories/ ├── dev/ │ ├── index.yaml │ └── myapp-1.1.0-dev.tgz ├── staging/ │ ├── index.yaml │ └── myapp-1.0.0.tgz └── production/ ├── index.yaml └── myapp-1.0.0.tgz

Automation Script:

#!/bin/bash # promote-chart.sh CHART_NAME=$1 VERSION=$2 FROM_ENV=$3 TO_ENV=$4 # Validate inputs if [ -z "$CHART_NAME" ] || [ -z "$VERSION" ] || [ -z "$FROM_ENV" ] || [ -z "$TO_ENV" ]; then echo "Usage: $0 <chart> <version> <from-env> <to-env>" exit 1 fi # Copy chart package cp "repositories/$FROM_ENV/$CHART_NAME-$VERSION.tgz" \ "repositories/$TO_ENV/" # Regenerate index helm repo index "repositories/$TO_ENV/" \ --url "https://charts.company.com/$TO_ENV" \ --merge "repositories/$TO_ENV/index.yaml" echo "Promoted $CHART_NAME:$VERSION from $FROM_ENV to $TO_ENV" # Usage: # ./promote-chart.sh myapp 1.0.0 staging production

Repository Maintenance

Regular Updates:

# Update repository index after adding new charts helm repo index . --url https://charts.company.com --merge index.yaml # Verify index integrity helm repo index . --url https://charts.company.com --validate

Cleanup Old Versions:

#!/bin/bash # cleanup-old-charts.sh CHART_NAME=$1 KEEP_VERSIONS=5 # List all versions versions=$(ls -1 ${CHART_NAME}-*.tgz | sort -V) # Count versions total=$(echo "$versions" | wc -l) if [ "$total" -gt "$KEEP_VERSIONS" ]; then # Delete oldest versions delete_count=$((total - KEEP_VERSIONS)) echo "$versions" | head -n $delete_count | xargs rm -v # Regenerate index helm repo index . --url https://charts.company.com echo "Cleaned up $delete_count old versions of $CHART_NAME" fi

Repository Backup:

#!/bin/bash # backup-repository.sh REPO_DIR="/var/www/charts" BACKUP_DIR="/backups/helm-charts" DATE=$(date +%Y-%m-%d) # Create backup tar -czf "$BACKUP_DIR/charts-$DATE.tar.gz" "$REPO_DIR" # Keep only last 30 days find "$BACKUP_DIR" -name "charts-*.tar.gz" -mtime +30 -delete echo "Repository backed up to $BACKUP_DIR/charts-$DATE.tar.gz"

Common Pitfalls

Forgetting to Update Index: Adding charts without regenerating index.yaml makes them invisible to Helm.

Incorrect URLs: Mismatched URLs in index.yaml and actual chart locations cause download failures.

Missing HTTPS: Serving repositories over HTTP exposes chart contents and credentials.

No Access Control: Public repositories for internal charts leak proprietary information.

Overwriting Versions: Publishing the same version twice with different content breaks reproducibility.

No Backup Strategy: Repository failures without backups cause production outages.

Key Takeaways