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
- Helm repositories are HTTP servers hosting packaged charts (.tgz) and an index.yaml metadata file
- The index.yaml file contains chart names, versions, URLs, checksums, and metadata for discovery
- Public repositories like Bitnami and Prometheus Community provide thousands of pre-built charts
- Simple HTTP servers (NGINX, Apache) can serve as chart repositories with minimal configuration
- GitHub Pages offers free repository hosting with automated publishing via GitHub Actions
- ChartMuseum provides a dedicated repository server with API, authentication, and cloud storage backends
- OCI registries (Docker Hub, GHCR, Harbor) support Helm charts without requiring index.yaml files
- Use HTTPS and authentication (basic auth, bearer tokens, client certificates) to secure repositories
- Follow SemVer for chart versioning - patch for fixes, minor for features, major for breaking changes
- Separate repositories or directories for different environments (dev, staging, production)
- Regenerate index.yaml with
helm repo indexafter adding or removing charts - The --merge flag preserves existing index entries when adding new charts
- Automate chart promotion between environments with scripts that copy packages and update indexes
- Regular backups of repository data prevent loss of charts and historical versions
- Clean up old chart versions periodically while maintaining recent versions for rollbacks