Deploy Helm Charts
This tutorial walks you through deploying a Helm chart from an OCM component version to a Kubernetes cluster, using the OCM Controllers with kro and Flux.
What You’ll Learn
By the end of this tutorial, you will:
- Create and publish an OCM component version that references a Helm chart
- Define a ResourceGraphDefinition to orchestrate OCM and Flux resources
- Deploy the Helm chart to your cluster using the OCM Controllers
Estimated time
~20 minutes
Scenario
You as a developer create an application, packaged as a Helm chart, and publish it as OCM component version in an OCI registry. Then, you as an operator deploy the Helm chart into a Kubernetes cluster using the OCM Controllers. You define a ResourceGraphDefinition that tells kro how to orchestrate the OCM and Flux resources to deploy the Helm chart.
Prerequisites
- Controller environment set up (OCM Controllers, kro and Flux in a Kubernetes cluster)
- OCM CLI installed
- Access to an OCI registry (e.g., ghcr.io)
Create and Publish a Component Version
Create a working directory
mkdir /tmp/helm-deploy && cd /tmp/helm-deployDefine the component
Create a
component-constructor.yamlfile that includes a Helm chart resource:components: - name: ocm.software/ocm-k8s-toolkit/simple provider: name: ocm.software version: "1.0.0" resources: - name: helm-resource type: helmChart version: 1.0.0 access: type: ociArtifact imageReference: "ghcr.io/stefanprodan/charts/podinfo:6.9.1@sha256:565d310746f1fa4be7f93ba7965bb393153a2d57a15cfe5befc909b790a73f8a"This component references the
podinfoHelm chart, a simple web application that displays pod information.Build the component version
Now add the component version to a local Common Transport Format (CTF) archive. By default, the
ocm add cvcommand looks for a file calledcomponent-constructor.yamlin the current directory, and creates a CTF archive in a folder calledtransport-archive:ocm add cvYou should see this output
time=2026-02-25T12:47:09.622+01:00 level=WARN msg="could not get credential consumer identity for component version repository" repository=transport-archive error="failed to get component version repository: cannot resolve consumer identity for ctf: credentials not supported" COMPONENT │ VERSION │ PROVIDER ─────────────────────────────────────┼─────────┼────────────── ocm.software/ocm-k8s-toolkit/simple │ 1.0.0 │ ocm.softwareTransfer to your registry
Use
ocm transfer cvand specify the correct reference (<path-to-your-ctf>//<component>:<version>) and target repository. Replace<your-namespace>with your registry namespace:📣 Note: 📣
If your registry requires authentication, configure Credentials for OCM CLI first.ocm transfer cv transport-archive//ocm.software/ocm-k8s-toolkit/simple:1.0.0 ghcr.io/<your-namespace>You should see this output
Transferring component versions... ✓ transformOcmSoftwareOcmK8sToolkitSimple100Upload [OCIAddComponentVersion] [████████████████████████████████████████] 100% 1/1To make your component public in GitHub Container Registry, go to the
packagestab in your GitHub repositoryhttps://github.com/<your-namespace>?tab=packages, select the packagecomponent-descriptors/ocm.software/ocm-k8s-toolkit/simple, and under “Package settings” change the visibility topublic.Verify the upload
ocm get cv ghcr.io/<your-namespace>//ocm.software/ocm-k8s-toolkit/simple:1.0.0You should see this output
COMPONENT │ VERSION │ PROVIDER ─────────────────────────────────────┼─────────┼────────────── ocm.software/ocm-k8s-toolkit/simple │ 1.0.0 │ ocm.software
Deploy Helm chart using the OCM Controllers
Create ResourceGraphDefinition
The ResourceGraphDefinition tells kro how to orchestrate the OCM and Flux resources.
Creatergd.yamlwith the following content, replacing<your-namespace>with your registry namespace:apiVersion: kro.run/v1alpha1 kind: ResourceGraphDefinition metadata: name: simple spec: schema: apiVersion: v1alpha1 # The name of the CRD that is created by this ResourceGraphDefinition when applied kind: Simple spec: # This spec defines values that can be referenced in the ResourceGraphDefinition and that can be set in the # instances of this ResourceGraphDefinition. # We will use it to pass a value to the Helm chart and configure the message the application shows # (see resource HelmRelease). message: string | default="foo" resources: # Repository points to the OCM repository in which the OCM component version is stored and checks if it is # reachable by pinging it. - id: repository template: apiVersion: delivery.ocm.software/v1alpha1 kind: Repository metadata: name: simple-repository spec: repositorySpec: baseUrl: ghcr.io/<your-namespace> type: OCIRegistry interval: 1m # ocmConfig is required, if the OCM repository requires credentials to access it. # ocmConfig: # Component refers to the Repository, downloads and verifies the OCM component version descriptor. - id: component template: apiVersion: delivery.ocm.software/v1alpha1 kind: Component metadata: name: simple-component spec: repositoryRef: name: ${repository.metadata.name} component: ocm.software/ocm-k8s-toolkit/simple semver: 1.0.0 interval: 1m # ocmConfig is required, if the OCM repository requires credentials to access it. # ocmConfig: # Resource points to the Component, downloads the resource passed by reference-name and verifies it. It then # publishes the location of the resource in its status. - id: resourceChart template: apiVersion: delivery.ocm.software/v1alpha1 kind: Resource metadata: name: simple-resource spec: componentRef: name: ${component.metadata.name} resource: byReference: resource: name: helm-resource # This must match the resource name set in the OCM component version (see above) additionalStatusFields: # The additional status fields are useful for splitting the imageReference into its components, so that # they can be used in depending deployers # Example: ghcr.io/stefanprodan/charts/podinfo:6.7.1 would be # registry: ghcr.io # repository: stefanprodan/charts/podinfo # reference/tag: 6.7.1 registry: resource.access.imageReference.toOCI().registry repository: resource.access.imageReference.toOCI().repository tag: resource.access.imageReference.toOCI().tag interval: 1m # ocmConfig is required, if the OCM repository requires credentials to access it. # ocmConfig: # OCIRepository watches and downloads the resource from the location provided by the Resource status. # The Helm chart location (url) refers to the status of the above resource. - id: ocirepository template: apiVersion: source.toolkit.fluxcd.io/v1 kind: OCIRepository metadata: name: simple-ocirepository spec: interval: 1m0s layerSelector: mediaType: "application/vnd.cncf.helm.chart.content.v1.tar+gzip" operation: copy url: oci://${resourceChart.status.additional.registry}/${resourceChart.status.additional.repository} ref: tag: ${resourceChart.status.additional.tag} # secretRef is required, if the OCI repository requires credentials to access it. # secretRef: # HelmRelease refers to the OCIRepository, lets you configure the Helm chart and deploys the Helm chart into the # Kubernetes cluster. - id: helmrelease template: apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: simple-helmrelease spec: releaseName: simple interval: 1m timeout: 5m chartRef: kind: OCIRepository name: ${ocirepository.metadata.name} namespace: default values: # We configure the Helm chart using Flux's HelmRelease 'values' field. We pass the value that we set in # the instance of the CRD created by the ResourceGraphDefinition (see below). ui: message: ${schema.spec.message}Apply the ResourceGraphDefinition
kubectl apply -f rgd.yamlYou should see this output
resourcegraphdefinition.kro.run/simple createdVerify it’s active:
kubectl get rgdYou should see this output
NAME APIVERSION KIND STATE AGE simple v1alpha1 Simple Active 19sA new Custom Resource Definition called
Simplethat you can now instantiate has been created.Create an instance
Create
instance.yamlto deploy the application:apiVersion: kro.run/v1alpha1 kind: Simple metadata: name: simple spec: message: "Deployed with OCM!"Deploy the application
kubectl apply -f instance.yamlYou should see this output
simple.kro.run/simple createdWait for the deployment to complete:
kubectl get simple -wYou should see this output
NAME STATE SYNCED AGE simple ACTIVE True 2mVerify the deployment
Check that the pod is running:
kubectl get deploymentsOutput:
NAME READY UP-TO-DATE AVAILABLE AGE simple-podinfo 1/1 1 1 3mVerify the custom message you configured in the HelmRelease values is showing up in the application:
kubectl get pods -l app.kubernetes.io/name=simple-podinfo \ -o jsonpath='{.items[0].spec.containers[0].env[?(@.name=="PODINFO_UI_MESSAGE")].value}'Output:
Deployed with OCM!
Troubleshooting
Authentication Errors
If you see errors like:
failed to list versions: response status code 401: unauthorizedYour registry package may be private. Either:
- Make the package public in your registry settings
- Configure credentials for the OCM Controller resources
Resource Not Found
If the component isn’t found, verify:
- The component was transferred successfully:
ocm get cv ghcr.io/<your-namespace>//ocm.software/ocm-k8s-toolkit/simple:1.0.0 - The
baseUrlin the ResourceGraphDefinition matches your registry
Cleanup
Remove the deployed resources:
kubectl delete -f instance.yaml
kubectl delete -f rgd.yamlNext Steps
- Tutorial: Structure Software Products with OCM - Learn how to structure complex applications with multiple components and resources
- Tutorial: Deploy a Helm Chart (with Bootstrap) - Use the OCM Controllers to deploy a Helm chart without manual bootstrapping, using GitOps
Related Documentation
- Concept: OCM Controllers - Learn about the architecture and capabilities of the OCM Controllers