Helm Plugin

The Helm plugin provides support for Kubernetes Helm chart repositories.

Overview

Status: ✅ Available

The Helm plugin consists of:

  • HelmSyncer - Syncs Helm charts from upstream chart repositories

  • HelmPublisher - Publishes Helm chart repositories with metadata

Features

  • ✅ index.yaml parsing

  • ✅ Helm chart downloading (.tgz files)

  • ✅ SHA256 checksum verification

  • ✅ Pattern-based chart filtering

  • ✅ Version filtering (only latest)

  • ✅ Metadata generation (index.yaml)

  • ✅ Chart deduplication via content-addressed storage

  • ✅ Snapshot support

  • Mirror Mode - Byte-for-byte identical repositories with snapshot versioning

  • Hosted Mode - Upload-only repositories for self-hosted charts (chantal package upload)

  • ℹ️ OCI ingest only — oci:// chart URLs referenced inside an upstream HTTP index.yaml are pulled via the helm binary (which must be installed). You cannot configure an oci:// feed, and Chantal never publishes charts over OCI (published repos are always plain HTTP with an index.yaml).

  • ℹ️ Charts are served unmodified; Chantal does not sign or re-sign charts

  • ⛔ Not supported: Helm provenance — upstream .prov files are not downloaded or republished, and helm pull --verify against a mirror will fail

Repository Modes

The default mode is filtered. Set mode: mirror explicitly for a byte-for-byte identical repository copy.

Note on the Helm index: the sync step always stores the upstream index.yaml in the content-addressed pool as a RepositoryFile, but how the published index.yaml is produced is driven by the mode: mirror republishes the stored upstream index verbatim, while filtered (and hosted) regenerate the index from the published charts so it lists exactly what was published.

Mirror Mode

Status: ✅ Available

In mirror mode, Chantal stores the original index.yaml metadata file in the content-addressed pool as a RepositoryFile. When publishing, the original metadata is hardlinked from the pool to the published directory.

Benefits:

  • Byte-for-byte identical to upstream repository

  • Snapshot versioning of metadata (track index.yaml changes over time)

  • Metadata deduplication across repositories and snapshots

  • Historical tracking of metadata changes

How it works:

  1. Sync Process:

    • Downloads index.yaml from upstream

    • Stores index.yaml in content-addressed pool by SHA256

    • Creates RepositoryFile database record

    • Links metadata to repository/snapshot

  2. Publish Process:

    • Queries RepositoryFile for stored index.yaml

    • Hardlinks original index.yaml from pool to published directory

    • Creates hardlinks for all chart .tgz files

    • Result: Byte-for-byte identical copy of upstream

Example:

repositories:
  - id: ingress-nginx
    name: Ingress NGINX Helm Charts
    type: helm
    feed: https://kubernetes.github.io/ingress-nginx
    enabled: true
    mode: mirror   # explicit; the default is 'filtered'

Use Cases:

  • Offline/air-gapped environments requiring exact upstream mirrors

  • Compliance requirements for unmodified upstream metadata

  • Snapshot versioning for reproducible deployments

  • Bandwidth optimization (metadata reused across snapshots)

Dynamic Index Generation

In filtered and hosted mode the publisher generates index.yaml from the chart metadata in the database rather than republishing an upstream index. (Mirror mode also falls back to generation if no stored index.yaml is available.)

This path:

  • Generates index.yaml from HelmMetadata in the database

  • Produces an index covering exactly the charts that were published — a filtered repo’s index lists only the charts that passed the filters

  • Supports post-processing (e.g., only latest versions), so the index reflects the post-processed set as well

Hosted Mode

An upload-only repository with no upstream feed. Custom-built charts are added with chantal package upload; there is nothing to sync, so chantal sync skips hosted repositories. Publishing generates index.yaml from the uploaded charts (the dynamic-generation path above), so the result is consumable by a real helm client.

repositories:
  - id: internal-charts
    name: Internal Helm Charts
    type: helm
    enabled: true
    mode: hosted
    # note: no 'feed' - hosted repos hold only uploaded charts

Upload one or more local chart .tgz files and publish:

# A single chart
chantal package upload --repo-id internal-charts --file ./demo-0.1.0.tgz

# A whole directory (optionally recursive)
chantal package upload --repo-id internal-charts --directory ./charts/ --recursive

# Replace an existing chart of the same name/version with different content
chantal package upload --repo-id internal-charts --file ./demo-0.1.0.tgz --force

# Regenerate index.yaml so clients can pull
chantal publish repo --repo-id internal-charts --target /srv/repos/internal-charts

Chart metadata is read from the chart’s top-level Chart.yaml in pure Python (the gzipped tar is opened directly) - no helm binary is required. Uploads are content-addressed and deduplicated by SHA-256: re-uploading identical bytes just links the existing pool entry, while a different build of a chart name/version already present requires --force.

Clients consume it like any other Helm HTTP repository:

helm repo add internal http://mirror.example.com/repos/internal-charts
helm repo update
helm pull internal/demo --version 0.1.0

Configuration

Basic Helm Repository

repositories:
  - id: ingress-nginx
    name: Ingress NGINX Helm Charts
    type: helm
    feed: https://kubernetes.github.io/ingress-nginx
    enabled: true

With Filters

repositories:
  - id: bitnami-databases
    name: Bitnami Charts - Databases Only
    type: helm
    feed: https://charts.bitnami.com/bitnami
    enabled: true
    filters:
      patterns:
        include: ["^postgresql$", "^mysql$", "^mongodb$", "^redis$"]
      post_processing:
        only_latest_version: true

With Authentication

Some Helm repositories require authentication:

repositories:
  - id: private-charts
    name: Private Helm Charts
    type: helm
    feed: https://charts.example.com/
    enabled: true
    ssl:
      ca_bundle: /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
      client_cert: /etc/pki/helm/client.pem
      client_key: /etc/pki/helm/client-key.pem
      verify: true

How It Works

Sync Process

  1. Fetch index.yaml

    GET https://charts.example.com/index.yaml
    

    Parse to find available charts and versions

  2. Parse chart metadata

    • Chart name and version

    • Chart URL (may be relative or absolute)

    • Chart digest (SHA256)

    • Dependencies, maintainers, etc.

  3. Apply filters

    • Pattern matching (include/exclude regex)

    • Version filtering (only latest)

  4. Download charts

    • Download .tgz files to content-addressed pool

    • Verify SHA256 checksums

    • Deduplicate identical charts

  5. Store metadata

    • Create ContentItem records

    • Store Helm metadata in database

    • Link charts to repository

Publish Process

  1. Query database for charts in repository/snapshot

  2. Create directory structure for published repository

  3. Create hardlinks from pool to published directory

  4. Generate index.yaml with chart metadata and URLs

  5. Set correct file permissions for web server access

Chart Filtering

Pattern Filters

Include specific charts by name:

filters:
  patterns:
    include:
      - "^nginx-.*"       # All nginx-related charts
      - "^prometheus$"    # Exact match: prometheus chart
      - "^grafana-.*"     # All grafana-related charts

Exclude charts by pattern:

filters:
  patterns:
    exclude:
      - ".*-alpha$"       # Exclude alpha versions
      - ".*-beta$"        # Exclude beta versions
      - "^deprecated-.*"  # Exclude deprecated charts

Version Filtering

Keep only the latest version of each chart:

filters:
  post_processing:
    only_latest_version: true

This is useful for:

  • Reducing storage usage

  • Simplifying chart selection for users

  • Automatically staying current with upstream

Common Use Cases

Mirror Official Kubernetes Charts

repositories:
  - id: kubernetes-charts
    name: Official Kubernetes Charts
    type: helm
    feed: https://kubernetes.github.io/ingress-nginx
    enabled: true

Mirror Bitnami Charts (Selective)

repositories:
  - id: bitnami-webservers
    name: Bitnami - Web Servers
    type: helm
    feed: https://charts.bitnami.com/bitnami
    enabled: true
    filters:
      patterns:
        include: ["^nginx$", "^apache$"]
      post_processing:
        only_latest_version: true

Mirror Harbor Registry

repositories:
  - id: harbor
    name: Harbor Helm Charts
    type: helm
    feed: https://helm.goharbor.io
    enabled: true

Private Chart Repository

repositories:
  - id: company-charts
    name: Company Internal Charts
    type: helm
    feed: https://charts.internal.company.com/
    enabled: true
    ssl:
      client_cert: /etc/pki/helm/company-client.pem
      client_key: /etc/pki/helm/company-client-key.pem
      verify: true

Publishing Helm Repositories

Publish Latest Repository

chantal publish repo --repo-id ingress-nginx

Published structure:

/var/www/repos/ingress-nginx/latest/
├── index.yaml
├── ingress-nginx-4.0.15.tgz
├── ingress-nginx-4.0.14.tgz
└── ...

Publish Snapshot

chantal snapshot create --repo-id ingress-nginx --name 2025-01-10
chantal publish snapshot --snapshot 2025-01-10 --repo-id ingress-nginx

Published structure:

/var/www/repos/ingress-nginx/snapshots/2025-01-10/
├── index.yaml
└── ingress-nginx-4.0.15.tgz

Configure Helm Client

Point Helm CLI to your mirrored repository:

# Add repository
helm repo add ingress-nginx http://mirror.example.com/repos/ingress-nginx/latest/

# Update repository index
helm repo update

# Install chart
helm install my-ingress ingress-nginx/ingress-nginx

Configure Helm with Snapshot

Use a specific snapshot for reproducible deployments:

# Add snapshot repository
helm repo add ingress-nginx-2025-01-10 \
  http://mirror.example.com/repos/ingress-nginx/snapshots/2025-01-10/

# Install from snapshot
helm install my-ingress ingress-nginx-2025-01-10/ingress-nginx

Chart Metadata

Chantal stores comprehensive metadata for each chart:

  • name - Chart name

  • version - Chart version (SemVer)

  • description - Chart description

  • home - Project home page URL

  • sources - Source code URLs

  • keywords - Chart keywords

  • maintainers - Maintainer information

  • icon - Chart icon URL

  • appVersion - Application version

  • deprecated - Deprecation status

  • annotations - Chart annotations

  • dependencies - Chart dependencies

  • type - Chart type (application/library)

  • apiVersion - Helm API version

This metadata is:

  • Stored in the database for querying

  • Included in published index.yaml

  • Available via Chantal CLI commands

Troubleshooting

Charts Not Syncing

Check index.yaml is accessible:

curl -I https://charts.example.com/index.yaml

Verify repository configuration:

chantal repo show --repo-id my-helm-repo

Check sync logs for errors:

chantal repo sync --repo-id my-helm-repo

Publishing Issues

Verify charts were synced:

chantal content list --repo-id my-helm-repo

Check published directory permissions:

ls -la /var/www/repos/my-helm-repo/latest/

Verify index.yaml was generated:

cat /var/www/repos/my-helm-repo/latest/index.yaml

Client Certificate Issues

Verify certificate files exist and are readable:

ls -la /etc/pki/helm/client.pem
ls -la /etc/pki/helm/client-key.pem

Test with curl:

curl --cert /etc/pki/helm/client.pem \
     --key /etc/pki/helm/client-key.pem \
     https://charts.example.com/index.yaml

Best Practices

  1. Use snapshots for production - Create dated snapshots for reproducible deployments

  2. Filter by patterns - Only mirror charts you need to reduce storage

  3. Keep latest only - Use only_latest_version: true unless you need version history

  4. Regular syncing - Schedule regular syncs to stay current

  5. Verify checksums - Chantal automatically verifies SHA256 checksums

  6. Document your mirrors - Keep notes on why specific charts are mirrored

  7. Test before production - Test snapshots in staging before promoting to production

Integration with CI/CD

GitOps Workflow

# ArgoCD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
spec:
  source:
    repoURL: http://mirror.example.com/repos/ingress-nginx/snapshots/2025-01-10/
    chart: ingress-nginx
    targetRevision: 4.0.15

Jenkins Pipeline

pipeline {
    stages {
        stage('Sync Helm Charts') {
            steps {
                sh 'chantal repo sync --repo-id ingress-nginx'
            }
        }
        stage('Create Snapshot') {
            steps {
                sh 'chantal snapshot create --repo-id ingress-nginx --name ${BUILD_ID}'
            }
        }
        stage('Publish Snapshot') {
            steps {
                sh 'chantal publish snapshot --snapshot ${BUILD_ID} --repo-id ingress-nginx'
            }
        }
    }
}

Further Reading