Install Helm Charts using the Chart resource

This is the second part of the tutorial on how to install Helm on Kubernetes using Pulumi.
In this tutorial, we will learn how to install a Helm chart on Kubernetes using the Pulumi Kubernetes provider and the Chart
resource. In the first part, we used the Release
resource to install a Helm chart on Kubernetes, here is the link to the first part: How to Install Helm Charts on Kubernetes with Pulumi - Part 1.
Most of the third-party applications that you want to install on your Kubernetes cluster, like whole monitoring stacks, databases, and other applications, are most likely available as Helm charts.
On services like Artifact Hub, you can find a lot of Helm charts that you can use to install applications on your Kubernetes cluster.
The Pulumi Kubernetes provider offers two different ways to install Helm on Kubernetes:
- Using the
Release
resource - Using the
Chart
resource
The Helm Chart resource renders the templates from your chart and then manages the objects directly with the Pulumi Kubernetes provider. There was recently a new version of the Chart
resource released, and in this blog post you will learn more about the improvements.
The Helm Release resource uses the Helm SDK to install the Helm chart on your Kubernetes cluster.
In this tutorial, you'll learn:
- How to install Helm on Kubernetes using Pulumi
Prerequisites:
- A Pulumi Cloud account and access token
- The Pulumi CLI
- A Kubernetes cluster (for example, kind)
- kubectl
- helm
Why using the Chart
resource over the Release
resource?
The Helm Chart
resource renders the templates from your chart and then manages the objects directly with the Pulumi Kubernetes provider. Chart
is implemented as a Component Resource
which provide a number of benefits for Pulumi users:
Benefits
- Visibility into all resources encapsulated by the Chart in Pulumi’s state, allowing users to directly query properties of individual resources.
- Tight integration with Pulumi’s Policy-as-Code framework -
CrossGuard
to enforce policies on all resources installed by Helm charts - Ability to leverage transformations to programmatically manipulate resources installed by Helm charts in any of the Pulumi supported programming languages
- Detailed previews and diffs rendered in the Pulumi CLI and Console for each Kubernetes resource resulting from Helm Chart config changes
We have seen significant adoption of Chart
over the years. However, since these resources are not directly managed by Helm, the following limitations apply:
Limitations
- No support for Helm Chart Hooks - i.e. equivalent of running
helm install
with the--no-hooks
option - No ability to import existing Helm releases into Pulumi state
- No interoperability using the Helm CLI on resources installed by Pulumi
Deploying a Helm chart with Chart
resource
To start, login to the Pulumi CLI and create a new Pulumi project. You can use the following command to create a new Pulumi project and select from the list of templates the kubernetes-<your-programming-language>
template.
# Choose your favorite Pulumi supported language
pulumi new kubernetes-<your-programming-language>
This will create a new Pulumi project with the necessary files to deploy Kubernetes resources and some example resources. Copy the following code snippet into your index.ts
, index.py
, index.go
, Program.cs
, or Pulumi.yaml
file.
import * as pulumi from "@pulumi/pulumi";
import * as kubernetes from "@pulumi/kubernetes";
const applyPatchForceAnnotation = async (args: pulumi.ResourceTransformArgs) => {
switch (args.type) {
case "kubernetes:helm.sh/v4:Chart":
break;
default:
args.props.metadata.annotations = {
"cost-center": "12345",
...args.props.metadata.annotations,
};
}
return {
props: args.props,
opts: args.opts,
};
};
const ns = new kubernetes.core.v1.Namespace("cert-manager", {
metadata: {
name: "cert-manager",
},
});
const certman = new kubernetes.helm.v4.Chart(
"cert-manager",
{
namespace: "cert-manager",
chart: "oci://registry-1.docker.io/bitnamicharts/cert-manager",
version: "1.3.1",
},
{ transforms: [applyPatchForceAnnotation] },
);
import pulumi
import pulumi_kubernetes as kubernetes
def apply_patchforce_annotation(args: pulumi.ResourceTransformArgs):
if not args.type_ == "kubernetes:helm.sh/v4:Chart":
if not "metadata" in args.props:
args.props["metadata"] = {}
if not "annotations" in args.props["metadata"]:
args.props["metadata"]["annotations"] = {}
args.props["metadata"]["annotations"]["cost-center"] = "12345"
return pulumi.ResourceTransformResult(props=args.props, opts=args.opts)
certman = kubernetes.helm.v4.Chart(
"cert-manager",
namespace="cert-manager",
chart="oci://registry-1.docker.io/bitnamicharts/cert-manager",
version="1.3.1",
opts=pulumi.ResourceOptions(transforms=[apply_patchforce_annotation])
)
package main
import (
"context"
helmv4 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/helm/v4"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi/internals"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func main() {
applyPatchForceAnnotation := func(ctx context.Context, rta *pulumi.XResourceTransformArgs) *pulumi.XResourceTransformResult {
transform := func(applier interface{}) {
o := rta.Props.ToMapOutputWithContext(ctx).ApplyT(applier)
r, err := internals.UnsafeAwaitOutput(ctx, o)
if err != nil {
panic(err)
}
rta.Props = r.Value.(pulumi.Map)
}
switch rta.Type {
case "kubernetes:helm.sh/v4:Chart":
// Do nothing for Helm charts
default:
transform(func(obj map[string]any) pulumi.Map {
// note: obj is an ordinary Unstructured object at this point.
err := unstructured.SetNestedField(obj, "12345", "metadata", "annotations", "cost-center")
if err != nil {
return nil
}
return pulumi.ToMap(obj)
})
}
return &pulumi.XResourceTransformResult{
Props: rta.Props,
Opts: rta.Opts,
}
}
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := helmv4.NewChart(ctx, "cert-manager", &helmv4.ChartArgs{
Namespace: pulumi.String("cert-manager"),
Chart: pulumi.String("oci://registry-1.docker.io/bitnamicharts/cert-manager"),
Version: pulumi.String("1.3.1"),
}, pulumi.Transforms([]pulumi.XResourceTransform{applyPatchForceAnnotation}))
if err != nil {
return err
}
return nil
})
}
using Pulumi;
using Pulumi.Experimental.Provider;
using Pulumi.Kubernetes.Helm.V4;
using Pulumi.Kubernetes.Types.Inputs;
using Pulumi.Kubernetes.Types.Inputs.Helm.V4;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Immutable;
await Deployment.RunAsync<MyStack>();
class MyStack : Stack
{
public MyStack()
{
var certman = new Chart("cert-manager", new ChartArgs
{
Namespace = "cert-manager",
Chart = "oci://registry-1.docker.io/bitnamicharts/cert-manager",
Version = "1.3.1",
},
new ComponentResourceOptions
{
ResourceTransforms =
{
async (args, _) =>
{
Console.WriteLine("Transforming resource");
if (args.Type != "kubernetes:helm.sh/v4:Chart")
{
var myTags = ImmutableDictionary.Create<string, object>().Add("cost-center", "12345");
InputMap<object> tags =
args.Args.TryGetValue("metadata", out var tagsValue) && tagsValue is not null ?
tagsValue is Output<ImmutableDictionary<string, object>> tagsOutput ? tagsOutput :
tagsValue is ImmutableDictionary<string, object> tagsDictionary ? tagsDictionary :
throw new InvalidOperationException($"Unexpected tags type: {tagsValue.GetType()}") :
ImmutableDictionary<string, object>.Empty;
tags = tags.Apply(t => t.SetItems(myTags));
return new( args.Args.SetItem("metadata", tags), args.Options);
}
return null;
},
},
});
}
}
name: helm-kubernetes-part-two-yaml
description: Demo Code Part 2 of the Tutorial around Helm in Kubernetes
runtime: yaml
resources:
cert-manager:
type: kubernetes:helm.sh/v4:Chart
properties:
namespace: cert-manager
chart: oci://registry-1.docker.io/bitnamicharts/cert-manager
version: "1.3.1"
Deploying the Helm chart
Now run the pulumi up
command to preview and deploy the resources you’ve just defined in your project.
Please choose a stack, or create a new one: dev
Previewing update (dev)
View in Browser (Ctrl+O): https://app.pulumi.com/dirien/helm-kubernetes-part-two-typescript/dev/previews/e88dbf07-434c-453c-b49c-dba176957abc
Type Name Plan
+ pulumi:pulumi:Stack helm-kubernetes-part-two-typescript-dev create
+ └─ kubernetes:helm.sh/v4:Chart cert-manager create
+ ├─ kubernetes:core/v1:ServiceAccount cert-manager:cert-manager/cert-manager-controller create
+ ├─ kubernetes:policy/v1:PodDisruptionBudget cert-manager:cert-manager/cert-manager-cainjector create
+ ├─ kubernetes:core/v1:ServiceAccount cert-manager:cert-manager/cert-manager-webhook create
+ ├─ kubernetes:core/v1:ServiceAccount cert-manager:cert-manager/cert-manager-cainjector create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:Role cert-manager:kube-system/cert-manager-cainjector-leader-election create
+ ├─ kubernetes:admissionregistration.k8s.io/v1:MutatingWebhookConfiguration cert-manager:cert-manager/cert-manager-webhook create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-view create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:RoleBinding cert-manager:cert-manager/cert-manager-webhook-dynamic-serving create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:RoleBinding cert-manager:kube-system/cert-manager-cainjector-leader-election create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-controller-controller-certificates create
+ ├─ kubernetes:core/v1:Service cert-manager:cert-manager/cert-manager-webhook create
+ ├─ kubernetes:networking.k8s.io/v1:NetworkPolicy cert-manager:cert-manager/cert-manager-controller create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-webhook-subjectaccessreviews create
+ ├─ kubernetes:core/v1:Service cert-manager:cert-manager/cert-manager-controller-metrics create
+ ├─ kubernetes:networking.k8s.io/v1:NetworkPolicy cert-manager:cert-manager/cert-manager-cainjector create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-controller-controller-orders create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-controller-controller-challenges create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-controller-controller-issuers create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-edit create
+ ├─ kubernetes:networking.k8s.io/v1:NetworkPolicy cert-manager:cert-manager/cert-manager-webhook create
+ ├─ kubernetes:policy/v1:PodDisruptionBudget cert-manager:cert-manager/cert-manager-controller create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-controller-orders create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-controller-controller-approve create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-controller-controller-clusterissuers create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-cainjector create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-controller-controller-ingress-shim create
+ ├─ kubernetes:admissionregistration.k8s.io/v1:ValidatingWebhookConfiguration cert-manager:cert-manager/cert-manager-webhook create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-webhook-subjectaccessreviews create
+ ├─ kubernetes:policy/v1:PodDisruptionBudget cert-manager:cert-manager/cert-manager-webhook create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:RoleBinding cert-manager:kube-system/cert-manager-controller-leader-election create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:Role cert-manager:kube-system/cert-manager-controller-leader-election create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-controller-certificates create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-controller-challenges create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-controller-clusterissuers create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-cainjector create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-controller-ingress-shim create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:Role cert-manager:cert-manager/cert-manager-webhook-dynamic-serving create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-controller-approve create
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-controller-issuers create
+ ├─ kubernetes:apps/v1:Deployment cert-manager:cert-manager/cert-manager-cainjector create
+ ├─ kubernetes:apps/v1:Deployment cert-manager:cert-manager/cert-manager-webhook create
+ └─ kubernetes:apps/v1:Deployment cert-manager:cert-manager/cert-manager-controller create
Resources:
+ 44 to create
Do you want to perform this update? yes
Updating (dev)
View in Browser (Ctrl+O): https://app.pulumi.com/dirien/helm-kubernetes-part-two-typescript/dev/updates/4
Type Name Status
+ pulumi:pulumi:Stack helm-kubernetes-part-two-typescript-dev created (37s)
+ ├─ kubernetes:core/v1:Namespace cert-manager created (0.23s)
+ └─ kubernetes:helm.sh/v4:Chart cert-manager created
+ ├─ kubernetes:admissionregistration.k8s.io/v1:MutatingWebhookConfiguration cert-manager:cert-manager/cert-manager-webhook created (0.27s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:Role cert-manager:kube-system/cert-manager-controller-leader-election created (0.49s)
+ ├─ kubernetes:core/v1:Service cert-manager:cert-manager/cert-manager-controller-metrics created (11s)
+ ├─ kubernetes:core/v1:ServiceAccount cert-manager:cert-manager/cert-manager-controller created (0.71s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:RoleBinding cert-manager:kube-system/cert-manager-cainjector-leader-election created (2s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-controller-controller-certificates created (3s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-controller-controller-clusterissuers created (4s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-controller-controller-ingress-shim created (4s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-cainjector created (1s)
+ ├─ kubernetes:core/v1:ServiceAccount cert-manager:cert-manager/cert-manager-webhook created (1s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:Role cert-manager:kube-system/cert-manager-cainjector-leader-election created (1s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:Role cert-manager:cert-manager/cert-manager-webhook-dynamic-serving created (2s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-controller-controller-challenges created (3s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:RoleBinding cert-manager:kube-system/cert-manager-controller-leader-election created (3s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-controller-approve created (4s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-controller-controller-approve created (7s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-controller-orders created (6s)
+ ├─ kubernetes:core/v1:ServiceAccount cert-manager:cert-manager/cert-manager-cainjector created (7s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-edit created (5s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-controller-certificates created (6s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-controller-controller-issuers created (6s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-controller-controller-orders created (5s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:RoleBinding cert-manager:cert-manager/cert-manager-webhook-dynamic-serving created (7s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-controller-clusterissuers created (8s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-webhook-subjectaccessreviews created (8s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-controller-issuers created (8s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-cainjector created (5s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-view created (9s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-controller-ingress-shim created (7s)
+ ├─ kubernetes:policy/v1:PodDisruptionBudget cert-manager:cert-manager/cert-manager-cainjector created (9s)
+ ├─ kubernetes:admissionregistration.k8s.io/v1:ValidatingWebhookConfiguration cert-manager:cert-manager/cert-manager-webhook created (11s)
+ ├─ kubernetes:core/v1:Service cert-manager:cert-manager/cert-manager-webhook created (19s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRoleBinding cert-manager:cert-manager-webhook-subjectaccessreviews created (10s)
+ ├─ kubernetes:policy/v1:PodDisruptionBudget cert-manager:cert-manager/cert-manager-webhook created (11s)
+ ├─ kubernetes:rbac.authorization.k8s.io/v1:ClusterRole cert-manager:cert-manager-controller-controller-challenges created (10s)
+ ├─ kubernetes:networking.k8s.io/v1:NetworkPolicy cert-manager:cert-manager/cert-manager-cainjector created (12s)
+ ├─ kubernetes:policy/v1:PodDisruptionBudget cert-manager:cert-manager/cert-manager-controller created (10s)
+ ├─ kubernetes:networking.k8s.io/v1:NetworkPolicy cert-manager:cert-manager/cert-manager-controller created (12s)
+ ├─ kubernetes:apps/v1:Deployment cert-manager:cert-manager/cert-manager-cainjector created (17s)
+ ├─ kubernetes:networking.k8s.io/v1:NetworkPolicy cert-manager:cert-manager/cert-manager-webhook created (13s)
+ ├─ kubernetes:apps/v1:Deployment cert-manager:cert-manager/cert-manager-webhook created (19s)
+ └─ kubernetes:apps/v1:Deployment cert-manager:cert-manager/cert-manager-controller created (19s)
Resources:
+ 45 created
Duration: 39s
As you can see, the Pulumi CLI shows all the resources that will be created in your Kubernetes cluster. This is due to the Chart
resource that renders the templates from the Helm chart and then manages the objects directly with the Pulumi Kubernetes provider.
With following command you can check the resources created by the Helm chart where also successful annotated with the cost-center
label.
kubectl get svc -n cert-manager -o jsonpath="{..metadata.annotations}" | tr ' ' '\n' | sort | uniq
And you should see the resources that were created by the Helm chart.
{"cost-center":"12345"}
Housekeeping
Before moving on, tear down the resources that are part of your stack to avoid incurring any charges.
- Run
pulumi destroy
to tear down all resources. You’ll be prompted to make sure you really want to delete these resources. A destroy operation may take some time, since Pulumi waits for the resources to finish shutting down before it considers the destroy operation to be complete. - To delete the stack itself, run
pulumi stack rm
. Note that this command deletes all deployment history from the Pulumi Service.
Next steps
In this tutorial, you learned how to install Helm on Kubernetes using the Kubernetes provider from Pulumi and the Release
resource.
- Learn more about Pulumi and Kubernetes in the Kubernetes documentation.
- Learn more about the
Chart
resource in the Pulumi Kubernetes API documentation. - Try the out the
Release
tutorial to learn how to install Helm charts on Kubernetes using theRelease
resource. - Or give the tutorial about Creating Resources on Kubernetes a try.