Package & Push a Helm Chart to an OCI Registry

This guide walks you through a practical CI scenario: your Git repository already contains a Helm chart (Chart.yaml, templates/, values.yaml, etc.).

We'll use Pipeline to:

  1. Clone the repository with the git-clone Task
  2. Package the chart with Helm
  3. Push the built chart to an OCI registry (e.g., Harbor)

You will create a Pipeline with two Tasks:

  1. git-clone: clone your repo into a shared workspace
  2. helm: packages the chart and pushes the .tgz to your OCI registry

TOC

Prerequisites

  • A Kubernetes cluster (you can use minikube for local testing).
  • Tekton Pipelines installed on your cluster.
  • A Helm 3.8+ container image (Helm v3 with OCI support).
  • An OCI registry and repository path (e.g., oci://registry.example.com/charts).
  • Push credentials for your OCI registry as a Docker config JSON:
    • Create a Kubernetes Secret of type kubernetes.io/dockerconfigjson (example below).
  • A Git repository that contains a valid Helm chart (directory with Chart.yaml, templates/, values.yaml).
  • Git access to the repository:
    • Public repo: nothing special.
    • Private repo: create a Git credential Secret (SSH or basic-auth).

Why Docker config JSON? Helm uses the same auth config as Docker. We'll point Helm to it via HELM_REGISTRY_CONFIG.

Step-by-Step Instructions

Step 1: Create the Registry Credential Secret

You need a registry credential for your OCI registry as a Docker config JSON.

You can refer to the Prepare Registry Credential.

Step 2: Prepare helm image

You need a Helm 3.8+ container image (Helm v3 with OCI support) to run the helm command.

You can refer to the Discover Tool Image.

When searching by label, specify the image as helm, for example: -l operator.tekton.dev/tool-image=helm.

Step 3: Define the Pipeline

This Pipeline uses git-clone to fetch your repo, then calls helm to package and push your chart.

Please replace <helm-image> with your Helm image.

apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: package-and-push-helm-oci
spec:
  description: Clone repo, package Helm chart, and push to an OCI registry.
  params:
    - name: repo_url
      type: string
      description: Git URL of your repository
    - name: revision
      type: string
      description: Git revision (branch, tag, or SHA)
      default: main
    - name: chart_dir
      type: string
      description: Path to the Helm chart in the repo
      default: "."
    - name: oci_repo
      type: string
      description: OCI repo, e.g., oci://registry.example.com/charts
    - name: package_flags
      type: string
      description: Extra flags for `helm package`
      default: ""
  workspaces:
    - name: source
    - name: registry-creds
      optional: true
    - name: basic-auth
      optional: true
  tasks:
    - name: git-clone
      taskRef:
        resolver: hub
        params:
          - name: catalog
            value: catalog
          - name: kind
            value: task
          - name: name
            value: git-clone
          - name: version
            value: "0.9"
      workspaces:
        - name: output
          workspace: source
        - name: basic-auth
          workspace: basic-auth
      params:
        - name: url
          value: $(params.repo_url)
        - name: revision
          value: $(params.revision)
    - name: helm
      runAfter:
        - "git-clone"
      taskRef:
        resolver: hub
        params:
          - name: catalog
            value: catalog
          - name: kind
            value: task
          - name: name
            value: run-script
          - name: version
            value: "0.1"
      workspaces:
        - name: source
          workspace: source
        - name: secret
          workspace: registry-creds
      params:
        - name: image
          ## Replace with your Helm image
          value: <helm-image>
        - name: script
          value: |
            if [ "$(workspaces.secret.bound)" = "true" ]; then
              echo "Using registry credentials in $(workspaces.secret.path)"
              export HELM_REGISTRY_CONFIG=$(workspaces.secret.path)/.dockerconfigjson
            fi

            echo "Packaging chart from $(params.chart_dir)"
            tempDir=$(mktemp -d)
            helm package $(params.chart_dir) --destination ${tempDir} $(params.package_flags)

            chart_tgz="$(ls ${tempDir}/*.tgz | head -n1)"
            if [ -z "${chart_tgz}" ]; then
              echo "No packaged chart found!"
              exit 1
            fi
            echo "Packaged: ${chart_tgz}"

            echo "Pushing to $(params.oci_repo)..."
            helm push "${chart_tgz}" "$(params.oci_repo)"

            echo "Done."

Step 4: Run It with a PipelineRun

Bind workspaces and pass your parameters.

apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  generateName: package-and-push-helm-oci-
spec:
  pipelineRef:
    name: package-and-push-helm-oci
  workspaces:
    - name: source
      volumeClaimTemplate:
        spec:
          ## Specify StorageClassName (as needed)
          # storageClassName: <storage-class-name>
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 1Gi
    - name: registry-creds
      secret:
        secretName: registry-creds
    - name: basic-auth
      secret:
        secretName: basic-auth
  params:
    - name: repo_url
      value: https://github.com/your-org/your-repo.git
    - name: revision
      value: main
    - name: oci_repo
      value: oci://registry.example.com/charts
    - name: chart_dir
      value: .

Troubleshooting

  • helm: command not found: Ensure your image actually contains the Helm binary.
  • Error: unknown command "push": Your Helm image lacks OCI push support. Use a newer Helm (3.8+) image.
  • unauthorized: authentication required: Ensure the Secret is correct and mounted to registry-creds. Confirm HELM_REGISTRY_CONFIG is set to that path.

Next Steps