Helm Repository Management: ChartMuseum and Harbor
Set up and manage private Helm chart repositories using ChartMuseum and Harbor, plus best practices for working with public chart repositories.
Private Helm chart repositories give teams controlled, auditable access to internal application packages. This guide covers ChartMuseum and Harbor as self-hosted options, plus strategies for working with public charts.
When to Use / When Not to Use
When to use private chart repositories
Private repos make sense when you have internal platform components that should not be public. Database operators, messaging middleware, monitoring stacks, and shared services that your teams install repeatedly benefit from a private repo with controlled access.
A private repo also helps when you need fine-grained access control. Harbor lets you set permissions per project, so one team cannot accidentally install another team’s charts.
Air-gapped environments essentially require private repos since your clusters cannot reach public chart repositories.
When to skip private repos
If you are a solo developer or small team shipping a single service, the operational overhead of maintaining ChartMuseum or Harbor is probably not worth it. A GitHub release with manually downloadable chart tarballs works fine for months.
If your organization already standardized on a different packaging system like ArgoCD ApplicationSets or raw kubectl with kustomize, adding Helm repos on top creates redundancy without clear benefit.
Repository Architecture Flow
flowchart LR
A[Developer] -->|helm package| B[CI/CD Pipeline]
B --> C{Push method}
C -->|OCI| D[Harbor<br/>Chart Registry]
C -->|HTTP API| E[ChartMuseum<br/>HTTP Server]
E --> F[index.yaml]
D --> G[Clusters pull<br/>via helm repo add]
F --> G
Chart Repository Basics
A Helm chart repository is simply a web server with an index.yaml file listing available charts and their tarballs. Users add your repo and Helm fetches the index:
helm repo add mycorp https://charts.mycorp.example.com
helm repo update
helm install mycorp/app ./mychart
The index.yaml contains chart metadata:
apiVersion: v2
entries:
myapp:
- version: 1.0.0
appVersion: "2.0"
created: "2026-01-15T10:30:00Z"
digest: sha256:a1b2c3d4e5f6...
urls:
- https://charts.mycorp.example.com/mychart-1.0.0.tgz
- version: 0.9.0
appVersion: "1.9"
created: "2025-12-01T08:00:00Z"
digest: sha256:b2c3d4e5f6a7...
urls:
- https://charts.mycorp.example.com/mychart-0.9.0.tgz
ChartMuseum Installation and Configuration
ChartMuseum is a lightweight, open-source chart repository server written in Go.
Deploy with Helm:
helm repo add stable https://charts.helm.sh/stable
helm repo update
helm install chartmuseum stable/chartmuseum \
--namespace chartmuseum \
--create-namespace \
--set persistence.enabled=true \
--set persistence.size=10Gi \
--set env.open.STORAGE=local \
--set service.type=LoadBalancer
Configuration options:
# Override values
env:
open:
STORAGE: local
STORAGE_LOCAL_ROOTDIR: /charts
DISABLE_API: "false"
AUTH_ANONYMOUS_GET: "true"
CHART_URL: https://charts.mycorp.example.com
ALLOWED_IMAGE_TYPES: images+helm-chart
persistence:
enabled: true
size: 50Gi
storageClass: standard-ssd
service:
type: ClusterIP
Upload charts via curl:
# Basic upload
curl -F "chart=@mychart-1.0.0.tgz" http://chartmuseum:8080/api/charts
# With basic auth
curl -u user:pass -F "chart=@mychart-1.0.0.tgz" \
http://chartmuseum:8080/api/charts
Automated upload in CI/CD:
#!/bin/bash
CHART_VERSION=$(helm show chart ./mychart | grep ^version: | awk '{print $2}')
helm package ./mychart
curl -u "$CHARTMUSEUM_USER:$CHARTMUSEUM_PASS" \
-F "chart=@mychart-${CHART_VERSION}.tgz" \
"$CHARTMUSEUM_URL/api/charts"
Harbor as Chart Registry
Harbor is an enterprise-grade registry that handles container images and Helm charts together. If you already use Harbor for container images, extending it for charts provides unified artifact management.
Enable chart repository in Harbor:
- Go to Administration > Registries > New endpoint
- Select Helm as the provider
- Configure the chart repository URL
Or enable via Harbor configuration in /etc/harbor/harbor.yml:
chart:
absolute_url: true
compression_rules:
- name: gzip
enabled: true
cache_duration: 24h
Push charts to Harbor:
# Login to Harbor
helm registry login harbor.mycorp.example.com
# Push chart directly as OCI artifact
helm chart push harbor.mycorp.example.com/myproject/mychart:1.0.0
# Or use http protocol
helm repo add mycorp https://harbor.mycorp.example.com/chartrepo/myproject
helm repo push ./mychart-1.0.0.tgz mycorp
Chart caching for air-gapped environments:
# Mirror a public chart to your private Harbor
helm chart pull stable/nginx
helm chart export stable/nginx mycorp/charts/
helm chart push mycorp/charts/nginx-1.0.0.tgz mycorp
Repository Caching Strategies
For organizations with multiple clusters or limited internet access, caching public charts locally improves reliability and speed.
ChartMuseum with cache:
# Pull and cache upstream charts
curl https://charts.helm.sh/stable/index.yaml -o /tmp/stable-index.yaml
curl https://charts.bitnami.com/index.yaml -o /tmp/bitnami-index.yaml
# Import into your ChartMuseum
curl -X POST http://mycache:8080/api/charts \
-F "chart=@/tmp/stable-index.yaml"
Sync automation script:
#!/bin/bash
# sync-charts.sh
UPSTREAM_REPOS="stable bitnami incubator"
CACHE_URL="http://chartmuseum:8080"
for repo in $UPSTREAM_REPOS; do
echo "Syncing $repo..."
helm repo add $repo https://charts.$repo.example.com
helm repo update $repo
# Get all chart versions
for chart in $(helm search repo $repo/ --format "{{.Name}}"); do
helm pull $chart -d /tmp/charts/
done
# Push to cache
for tgz in /tmp/charts/*.tgz; do
curl -F "chart=@$tgz" $CACHE_URL/api/charts
done
done
Chart Versioning and Deprecation
Managing chart lifecycles requires clear versioning policies and deprecation signals.
Version policies:
| Version Type | Format | Use |
|---|---|---|
| Major | 2.0.0 | Breaking changes |
| Minor | 1.5.0 | New features, backward compatible |
| Patch | 1.4.1 | Bug fixes |
Marking charts as deprecated:
# Chart.yaml
apiVersion: v2
name: old-chart
deprecated: true
version: 1.0.0
Helm treats deprecated charts specially:
# Search shows deprecated status
$ helm search repo mycorp/
NAME VERSION APP VERSION DEPRECATED
mycorp/old-chart 1.0.0 1.0 true
Automated deprecation notices:
# templates/notes.yaml
{{- if .Chart.Deprecated }}
WARNING: This chart is deprecated and will not receive updates.
Please migrate to: https://charts.mycorp.example.com/new-chart
{{- end }}
Private vs Public Chart Tradeoffs
When deciding between private and public repositories, consider these factors:
| Factor | Private Repository | Public Repository |
|---|---|---|
| Access Control | Fine-grained permissions | Limited |
| Internet Dependency | None (air-gapped friendly) | Requires internet |
| Maintenance | Your responsibility | Managed by provider |
| Discovery | Team-only | Anyone can find |
| Compliance | Full control | May have restrictions |
For most enterprise workloads, a private repository behind your VPN or in an air-gapped environment provides the best balance of security and usability. Public repositories work well for widely-adopted off-the-shelf software like nginx, Redis, or Prometheus where you want the convenience of community-maintained packages.
Production Failure Scenarios
Stale index.yaml after adding new charts
If you push a new chart version to ChartMuseum but forget to regenerate the index.yaml, users running helm repo update will not see the new version. Helm caches the index until the next update.
# After pushing a chart, regenerate index
helm repo index /path/to/charts --url https://charts.mycorp.example.com
# Then commit and push the updated index.yaml
ChartMuseum with API enabled handles this automatically when you use curl -F "chart=@..." to push.
Authentication expiring in CI
Long-running CI pipelines that pull charts hours after authenticating may fail when the Harbor or ChartMuseum token expires. Use short-lived tokens or re-authenticate before each pull.
# Re-authenticate before chart pull in long pipelines
helm registry login --username "$HARBOR_USER" --password "$HARBOR_PASS" harbor.mycorp.example.com
helm pull mycorp/mychart --version 1.0.0
Storage fills up on ChartMuseum
Without monitoring, ChartMuseum storage grows unbounded as you push new chart versions. Old versions accumulate, and if you use local storage, the disk eventually fills.
Set up storage cleanup policies or use an external object store (S3, GCS) with lifecycle policies to automatically delete old chart tarballs.
Harbor OCI vs repository mode confusion
Harbor supports charts in two modes: as OCI artifacts and as traditional chart repositories. Mixing modes or pushing OCI charts to a non-OCI project causes confusing errors.
Ensure your Harbor project has OCI artifact support enabled before pushing chart OCI artifacts.
Common Pitfalls / Anti-Patterns
No index.yaml regeneration
Pushing chart tarballs directly to the web server directory without regenerating index.yaml leaves the repository broken. Helm cannot discover charts that are not listed in the index.
Always regenerate index.yaml after adding or updating charts:
helm repo index /path/to/charts
Using latest tag in chart references
Referring to myapp:latest in your chart repository means Helm always pulls whatever was most recently pushed. This breaks reproducibility and makes rollbacks impossible since you cannot tell which version “latest” actually was.
Always pin to specific versions.
No access control on ChartMuseum
Leaving ChartMuseum with anonymous write access means anyone can upload any chart, including malicious ones. Use authentication and role-based access control.
Not mirroring critical public charts
Relying on public repositories like Bitnami without a local mirror means your air-gapped clusters cannot install those charts when the internet is unavailable. Mirror the charts you depend on.
Mixing chart sources without vetting
Not all public charts are created equal. Charts from unknown sources may have misconfigured resources, outdated dependencies, or security vulnerabilities. Vet public charts before recommending them to your team.
Observability Hooks
Track the health of your chart repository infrastructure with these monitoring practices.
Key metrics to monitor:
# ChartMuseum storage usage (bytes)
chartmuseum_storage_used_bytes
# Repository API request rate
sum(rate(chartmuseum_http_requests_total[5m])) by (method, status)
# Chart download latency
histogram_quantile(0.95, sum(rate(chartmuseum_request_duration_seconds_bucket[5m])) by (le))
# Harbor chart registry storage
harbor_chart_storage_used_bytes{project=~".*"}
# OCI artifact pull failures
sum(rate(oci_manifest_pulls_failed_total[5m])) by (repository, error_code)
Alert rules for chart repositories:
# Alert if ChartMuseum storage is above 80%
- alert: ChartMuseumStorageHigh
expr: chartmuseum_storage_used_bytes / chartmuseum_storage_limit_bytes > 0.8
labels:
severity: warning
annotations:
summary: "ChartMuseum storage above 80%"
description: "Chart repository storage is running low. Consider cleanup or expansion."
# Alert if Harbor chart registry is unavailable
- alert: HarborChartRegistryDown
expr: harbor_chart_registry_up == 0
labels:
severity: critical
annotations:
summary: "Harbor chart registry is down"
description: "Chart artifacts cannot be pulled from Harbor. Check Harbor status."
# Alert on high API error rate
- alert: ChartRepoHighErrorRate
expr: sum(rate(chartmuseum_http_requests_total{status=~"5.."}[5m])) / sum(rate(chartmuseum_http_requests_total[5m])) > 0.05
labels:
severity: warning
annotations:
summary: "Chart repository API error rate above 5%"
description: "High error rate on chart repository API. Check ChartMuseum logs."
Debugging commands:
# Check ChartMuseum health
curl http://chartmuseum:8080/health
# List all charts in ChartMuseum
curl http://chartmuseum:8080/api/charts | jq 'keys'
# Verify Harbor chart repository connectivity
helm repo add mycorp https://harbor.mycorp.example.com/chartrepo/myproject --debug
# Check OCI artifact manifest
oras manifest fetch myregistry.azurecr.io/myteam/mychart:1.0.0
# Verify chart signature
helm verify ./mychart-1.0.0.tgz
Quick Recap
Key Takeaways
- ChartMuseum works for lightweight single-team use; Harbor suits enterprise multi-team environments with image registry needs
- Always regenerate
index.yamlafter pushing charts - Mirror public charts you depend on if you operate in air-gapped environments
- Authentication tokens in CI need to be refreshed for long pipelines
- Harbor supports both OCI artifact mode and traditional chart repository mode; know which your project uses
Repository Checklist
# Add your private repo
helm repo add mycorp https://charts.mycorp.example.com
helm repo update
# Verify a chart is available
helm search repo mycorp/
# Pull a specific version
helm pull mycorp/mychart --version 1.0.0
# Check for deprecated charts
helm repo update
helm search repo mycorp/ --deployed
Conclusion
Managing Helm repositories effectively involves choosing the right infrastructure for your organization, whether that is ChartMuseum for lightweight simplicity or Harbor for unified artifact management across teams. Caching strategies reduce external dependencies, and clear versioning policies help users navigate chart lifecycles. For more deployment patterns, see our Helm Charts overview and CI/CD Pipelines guide.
Category
Related Posts
Developing Helm Charts: Templates, Values, and Testing
Create production-ready Helm charts with Go templates, custom value schemas, and testing using Helm unittest and ct.
Helm Versioning and Rollback: Managing Application Releases
Master Helm release management—revision history, automated rollbacks, rollback strategies, and handling failed releases gracefully.
OCI Artifacts: Distributing Container Images and Helm Charts
Package and distribute container images, Helm charts, and other artifacts using the OCI (Open Container Initiative) specification for portable artifact management.