Complete Guide: Setting Up and Publishing Helm Charts to ChartMuseum
ChartMuseum is an open-source Helm Chart Repository server that makes it easy to host and share Helm charts within your organization. This comprehensive guide covers everything from setting up ChartMuseum to automating chart publishing through CI/CD pipelines.
Table of Contents
1. Setting Up ChartMuseum
Install ChartMuseum in Kubernetes
First, add the ChartMuseum Helm repository and install it with the API enabled for chart uploads:
# Add ChartMuseum's Helm repohelm repo add chartmuseum https://chartmuseum.github.io/charts
# Install ChartMuseum with API enabled for uploadshelm install chartmuseum chartmuseum/chartmuseum \ --set env.open.DISABLE_API=false \ --set service.type=ClusterIP \ --set persistence.enabled=true \ --set persistence.size=10Gi
# For external access, set up an Ingresskubectl create ingress chartmuseum \ --rule="cm.yourdomain.com/*=chartmuseum:8080"
Verify Installation
Ensure ChartMuseum is running correctly:
# Check ChartMuseum is runningkubectl get pods -l app.kubernetes.io/name=chartmuseum
# Test the endpointcurl https://cm.yourdomain.com/health
Advanced Configuration Options
For production deployments, consider these additional settings:
# values.yaml for production deploymentenv: open: DISABLE_API: false AUTH_ANONYMOUS_GET: true # Allow anonymous chart downloads BASIC_AUTH_USER: admin BASIC_AUTH_PASS: secretpassword DEPTH: 2 # Allow nested chart directories
persistence: enabled: true size: 50Gi storageClass: fast-ssd
ingress: enabled: true annotations: cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/proxy-body-size: "100m" hosts: - cm.yourdomain.com tls: - secretName: chartmuseum-tls hosts: - cm.yourdomain.com
2. Creating a Helm Chart
Generate a New Chart
# Create a new charthelm create mychart
# Structuremychart/ ├── .helmignore # Files to ignore when packaging ├── Chart.yaml # Chart metadata ├── values.yaml # Default configuration values ├── charts/ # Dependencies └── templates/ # K8s resource templates
Configure Chart Metadata
Edit Chart.yaml
to set appropriate metadata:
apiVersion: v2name: mychartdescription: My application charttype: applicationversion: 0.1.0appVersion: "1.0.0"keywords: - app - microservicehome: https://github.com/yourorg/mychartsources: - https://github.com/yourorg/mychartmaintainers: - name: Anubhav Gain email: anubhav@example.com url: https://github.com/anubhavg-icplicon: https://example.com/mychart-icon.pngannotations: category: Backend
Best Practices for Chart Development
- Use Helper Templates: Create
_helpers.tpl
for reusable template snippets - Implement Health Checks: Include readiness and liveness probes
- Resource Limits: Set default CPU/memory limits in values.yaml
- Security Context: Define security contexts for pods
- Documentation: Include comprehensive README.md and NOTES.txt
3. Packaging the Chart
Basic Packaging
# Package the charthelm package ./mychart
# This creates: mychart-0.1.0.tgz
Signed Charts for Enhanced Security
Setting up GPG signing for charts:
# First, set up GPG keys if neededgpg --full-generate-key
# Choose:# - RSA and RSA (default)# - Key size: 4096# - Validity: 2y# - Real name: Your Name# - Email: your.email@example.com
# Export keys to format Helm can usegpg --export > ~/.gnupg/pubring.gpggpg --export-secret-keys > ~/.gnupg/secring.gpg
# Package with signinghelm package ./mychart --sign --key "Your Name <your.email@example.com>"
# This creates:# - mychart-0.1.0.tgz# - mychart-0.1.0.tgz.prov (provenance file)
# Verify packagehelm verify mychart-0.1.0.tgz
Advanced Packaging Options
# Package with custom destinationhelm package ./mychart --destination ./dist
# Package with dependency updatehelm dependency update ./mycharthelm package ./mychart
# Package with version overridehelm package ./mychart --version 1.2.3
# Package with app version overridehelm package ./mychart --app-version 2.0.0
4. Pushing to ChartMuseum
Method 1: Using the Helm CM-Push Plugin
The most convenient method using the official plugin:
# Install the ChartMuseum pluginhelm plugin install https://github.com/chartmuseum/helm-push
# Add your ChartMuseum repohelm repo add myrepo https://cm.yourdomain.com
# Push the charthelm cm-push mychart-0.1.0.tgz myrepo
# Push with force flag to overwritehelm cm-push mychart-0.1.0.tgz myrepo --force
# Push directly from chart directoryhelm cm-push ./mychart myrepo
Method 2: Using cURL
For environments where the plugin isn’t available:
# Push chart using cURLcurl --data-binary "@mychart-0.1.0.tgz" https://cm.yourdomain.com/api/charts
# If you've signed the chart, also push the .prov filecurl --data-binary "@mychart-0.1.0.tgz.prov" https://cm.yourdomain.com/api/prov
# If authentication is requiredcurl -u username:password --data-binary "@mychart-0.1.0.tgz" https://cm.yourdomain.com/api/charts
# With Bearer token authenticationcurl -H "Authorization: Bearer ${TOKEN}" --data-binary "@mychart-0.1.0.tgz" https://cm.yourdomain.com/api/charts
Method 3: Using CI/CD Integration
Example for pushing from CI/CD:
# Function to push chart with retrypush_chart() { local chart_file=$1 local repo_url=$2 local max_attempts=3 local attempt=1
while [ $attempt -le $max_attempts ]; do echo "Attempting to push $chart_file (attempt $attempt/$max_attempts)"
if curl --fail --data-binary "@${chart_file}" "${repo_url}/api/charts"; then echo "Successfully pushed $chart_file" return 0 fi
attempt=$((attempt + 1)) sleep 5 done
echo "Failed to push $chart_file after $max_attempts attempts" return 1}
# Usagepush_chart "mychart-0.1.0.tgz" "https://cm.yourdomain.com"
5. Managing Charts in ChartMuseum
Searching and Listing Charts
# Update your local repo to see new chartshelm repo update
# Search for your charthelm search repo myrepo/mychart
# View chart detailshelm show chart myrepo/mychart
# View chart valueshelm show values myrepo/mychart
# List all versionshelm search repo myrepo/mychart --versions
# List all charts in repohelm search repo myrepo/
Chart Version Management
# Delete a specific chart versioncurl -X DELETE https://cm.yourdomain.com/api/charts/mychart/0.1.0
# Delete all versions of a chartcurl -X DELETE https://cm.yourdomain.com/api/charts/mychart
# Get chart information via APIcurl https://cm.yourdomain.com/api/charts/mychart
# Get specific version infocurl https://cm.yourdomain.com/api/charts/mychart/0.1.0
Downloading Charts
# Download a charthelm pull myrepo/mychart
# Download specific versionhelm pull myrepo/mychart --version 0.1.0
# Download and untarhelm pull myrepo/mychart --untar
# Download to specific directoryhelm pull myrepo/mychart --destination ./downloads
6. Automating with CI/CD
GitHub Actions Workflow
Create .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: Set up Helm uses: azure/setup-helm@v3 with: version: v3.12.0
- name: Add Helm Push Plugin run: helm plugin install https://github.com/chartmuseum/helm-push
- name: Add ChartMuseum Repo run: | helm repo add myrepo https://cm.yourdomain.com \ --username ${{ secrets.CHARTMUSEUM_USER }} \ --password ${{ secrets.CHARTMUSEUM_PASS }}
- name: Package and Push Charts run: | for chart in ./charts/*; do if [ -d "$chart" ]; then echo "Processing chart: $chart"
# Update dependencies helm dependency update "$chart"
# Package the chart helm package "$chart"
# Get chart name and version chart_name=$(basename "$chart") chart_version=$(grep '^version:' "$chart/Chart.yaml" | awk '{print $2}') chart_file="${chart_name}-${chart_version}.tgz"
# Push to ChartMuseum helm cm-push "$chart_file" myrepo --force
# Clean up rm -f "$chart_file" fi done
- name: Update Helm Repo Index run: helm repo update
GitLab CI Pipeline
Create .gitlab-ci.yml
:
stages: - package - publish
variables: HELM_VERSION: "3.12.0" CHARTMUSEUM_URL: "https://cm.yourdomain.com"
package-charts: stage: package image: alpine/helm:${HELM_VERSION} script: - helm plugin install https://github.com/chartmuseum/helm-push - | for chart in ./charts/*; do if [ -d "$chart" ]; then helm dependency update "$chart" helm package "$chart" --destination ./dist fi done artifacts: paths: - dist/*.tgz expire_in: 1 hour
publish-charts: stage: publish image: alpine/helm:${HELM_VERSION} dependencies: - package-charts script: - helm plugin install https://github.com/chartmuseum/helm-push - helm repo add myrepo ${CHARTMUSEUM_URL} --username ${CHARTMUSEUM_USER} --password ${CHARTMUSEUM_PASS} - | for chart in ./dist/*.tgz; do helm cm-push "$chart" myrepo --force done only: - main
Jenkins Pipeline
pipeline { agent any
environment { CHARTMUSEUM_URL = 'https://cm.yourdomain.com' CHARTMUSEUM_CREDS = credentials('chartmuseum-credentials') }
stages { stage('Setup') { steps { sh ''' curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash helm plugin install https://github.com/chartmuseum/helm-push ''' } }
stage('Package Charts') { steps { sh ''' for chart in ./charts/*; do if [ -d "$chart" ]; then helm dependency update "$chart" helm package "$chart" fi done ''' } }
stage('Publish Charts') { steps { sh ''' helm repo add myrepo ${CHARTMUSEUM_URL} \ --username ${CHARTMUSEUM_CREDS_USR} \ --password ${CHARTMUSEUM_CREDS_PSW}
for chart in *.tgz; do helm cm-push "$chart" myrepo --force done ''' } } }
post { always { cleanWs() } }}
7. Troubleshooting Common Issues
API Disabled
If you get “not found” or 404 errors when pushing:
# Check if API is enabledcurl https://cm.yourdomain.com/api/charts
# Fix: Update ChartMuseum deploymentkubectl set env deployment/chartmuseum DISABLE_API=false
# Or edit the deploymentkubectl edit deployment chartmuseum# Add under spec.template.spec.containers[0].env:# - name: DISABLE_API# value: "false"
Authentication Issues
If authentication is required:
# Set credentials in environmentexport HELM_REPO_USERNAME=adminexport HELM_REPO_PASSWORD=password
# Then pushhelm cm-push mychart-0.1.0.tgz myrepo
# Or use inline credentialshelm repo add myrepo https://cm.yourdomain.com --username admin --password password
# For bearer token authexport HELM_REPO_ACCESS_TOKEN=your-tokenhelm cm-push mychart-0.1.0.tgz myrepo
GPG Key Issues
If chart signing fails:
# Check available keysgpg --list-secret-keys --keyid-format LONG
# Export keys in the format Helm expectsgpg --export > ~/.gnupg/pubring.gpggpg --export-secret-keys > ~/.gnupg/secring.gpgchmod 600 ~/.gnupg/pubring.gpg ~/.gnupg/secring.gpg
# If using GPG 2.1+, you might need legacy formatgpg --export-secret-keys --armor > private.keygpg --export --armor > public.key
Chart Already Exists
If you get an error about chart already existing:
# Use --force flaghelm cm-push mychart-0.1.0.tgz myrepo --force
# Or delete the old version firstcurl -X DELETE https://cm.yourdomain.com/api/charts/mychart/0.1.0
# Then push the new versionhelm cm-push mychart-0.1.0.tgz myrepo
Large Chart Files
For charts larger than default limits:
# Increase nginx ingress body sizekubectl annotate ingress chartmuseum nginx.ingress.kubernetes.io/proxy-body-size=100m
# For ChartMuseum itself, set MAX_UPLOAD_SIZEkubectl set env deployment/chartmuseum MAX_UPLOAD_SIZE=104857600 # 100MB in bytes
Debugging Push Failures
Enable verbose logging:
# For helm cm-pushhelm cm-push mychart-0.1.0.tgz myrepo --debug
# For curlcurl -v --data-binary "@mychart-0.1.0.tgz" https://cm.yourdomain.com/api/charts
# Check ChartMuseum logskubectl logs deployment/chartmuseum -f
Best Practices and Security Considerations
1. Access Control
- Use authentication for push operations
- Allow anonymous read for internal users
- Implement RBAC for different teams
- Use separate repositories for dev/staging/prod
2. Chart Versioning
- Follow semantic versioning strictly
- Never reuse version numbers
- Document breaking changes
- Maintain compatibility where possible
3. Storage and Backup
- Enable persistent storage
- Regular backups of chart storage
- Consider object storage (S3, GCS) for large deployments
- Implement retention policies
4. Monitoring
- Monitor ChartMuseum health endpoints
- Track storage usage
- Alert on push failures
- Log all API access
5. Security Scanning
- Scan charts for vulnerabilities
- Validate chart templates
- Check for hardcoded secrets
- Implement admission webhooks for validation
Real-World Example
Here’s a practical example from the gist:
# Package the invinsense-xdr charthelm package ./charts/invinsense-xdr
# Push to the invinsense repositoryhelm cm-push invinsense-xdr-1.0.0.tgz invinsense
This demonstrates a typical workflow for packaging and publishing a production chart.
Conclusion
ChartMuseum provides a simple yet powerful solution for hosting Helm charts within your organization. By following this guide, you can set up a robust chart repository with proper security, automation, and management practices. The combination of ChartMuseum with CI/CD automation enables teams to efficiently manage their Kubernetes applications through Helm charts, promoting consistency and reliability across deployments.
Remember to regularly update ChartMuseum and review your security settings to maintain a secure and efficient chart repository infrastructure.