You can deploy Helm charts as Crossplane managed resources, but it’s not quite as straightforward as just pointing Crossplane at a Helm repository. The trick is understanding how Crossplane’s Helm provider translates Helm’s declarative nature into Crossplane’s reconciliation loop.

Here’s a Kubernetes cluster running a simple Nginx deployment and service, managed by Crossplane’s Helm provider.

apiVersion: helm.crossplane.io/v1beta1
kind: Release
metadata:
  name: my-nginx
  namespace: crossplane-system # The namespace where the Helm Release will be created
spec:
  forProvider:
    chart:
      name: nginx
      version: "1.16.0" # Specify a fixed version for reproducibility
      repository: "https://charts.bitnami.com/bitnami"
    namespace: default # The namespace where Nginx will be installed
    values: |
      replicaCount: 2
      service:
        type: LoadBalancer
  providerConfigRef:
    name: default # Refers to your ProviderConfig for the Helm provider

This Release custom resource tells Crossplane to install the nginx chart from the Bitnami repository. Crossplane’s Helm provider will then interact with the Kubernetes cluster’s Helm client to deploy this chart. The values section allows you to pass in configuration just like you would with helm install.

The core problem Crossplane solves here is managing the lifecycle of Helm deployments within a declarative, GitOps-friendly framework. Instead of manually running helm install or helm upgrade, you declare the desired state in a Kubernetes YAML file. Crossplane then ensures the cluster’s state matches that declaration, reconciling any drift. This means Crossplane can install, upgrade, and delete Helm releases automatically based on changes to your Release custom resource.

Internally, the Helm provider for Crossplane acts as a controller. It watches for Release resources. When it sees a new Release, it calls the Helm SDK to perform an install operation. If the Release resource is modified (e.g., replicaCount changes), it triggers a helm upgrade. If the Release resource is deleted, Crossplane ensures the Helm release is uninstalled. It effectively bridges the gap between Kubernetes-native declarative management and the imperative world of Helm commands.

When you define a Release, you’re not just telling Crossplane what to install, but also how it should be managed. The providerConfigRef links this Release to a specific instance of the Helm provider. This provider instance is configured with credentials and endpoint information for the Kubernetes cluster where the Helm chart should be deployed. The forProvider block is where you specify Helm-specific details like the chart name, version, repository, and any custom values.

The most surprising thing about using Crossplane with Helm is that you can use it to manage any Kubernetes resource that can be templated by Helm. This includes not just applications, but also custom resources defined by other operators. Crossplane’s Helm provider doesn’t deeply understand the meaning of the resources deployed by Helm; it only understands the Helm release lifecycle. This means you can use Helm to package and deploy complex stacks of Kubernetes resources, and then manage those stacks declaratively through Crossplane.

One common pattern is to use Helm charts to deploy operators themselves, and then use Crossplane to manage the custom resources created by those operators. For example, you might deploy the Prometheus operator via a Helm chart using Crossplane, and then define Prometheus custom resources (CRs) directly in Crossplane, which are then picked up by the deployed Prometheus operator. This gives you a powerful, unified control plane for both infrastructure and application deployments.

The next step you’ll likely encounter is managing dependencies between Helm releases, or orchestrating Helm releases alongside other Crossplane-managed cloud resources.

Want structured learning?

Take the full Crossplane course →