# Terraform Helm Provider: Deploy Helm Charts to Kubernetes with OpenTofu

> Install Helm charts in a Kubernetes cluster with the Terraform and OpenTofu Helm provider: values, secrets, upgrades, ingress-nginx and cert-manager examples.

- Source: https://www.itwonderlab.com/terraform-helm-provider-kubernetes/
- Published: 2026-01-16
- Updated: 2026-01-16
- Author: Javier Ruiz Jiménez (https://www.javierruizjimenez.com/)
- Site: IT Wonder Lab (https://www.itwonderlab.com/)

---

## Manage Helm releases as code

[Helm](https://www.itwonderlab.com/install-kubernetes-helm/) is the package manager of Kubernetes. The Terraform and OpenTofu `helm` provider installs and upgrades charts as `helm_release` resources, so the cluster and its base components (ingress controller, certificate manager, monitoring) are created in the same pipeline. Compare with managing plain manifests in [Kubernetes with Terraform](https://www.itwonderlab.com/kubernetes-with-terraform/) and with [GitOps](https://www.itwonderlab.com/argocd-gitops-kubernetes/).

### Providers

The Helm provider needs access to the cluster. This example uses an [EKS](https://www.itwonderlab.com/terraform-eks/) cluster created in the same configuration:

```hcl title="providers.tf"
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    helm = {
      source  = "hashicorp/helm"
      version = "~> 2.17"
    }
  }
}

data "aws_eks_cluster" "this" {
  name = var.cluster_name
}

data "aws_eks_cluster_auth" "this" {
  name = var.cluster_name
}

provider "helm" {
  kubernetes {
    host                   = data.aws_eks_cluster.this.endpoint
    cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
    token                  = data.aws_eks_cluster_auth.this.token
  }
}
```

For a local cluster ([K3s](https://www.itwonderlab.com/install-kubernetes-k3s/)), use `config_path = "~/.kube/config"` inside the `kubernetes` block. Version 3 of the provider changes this block syntax to an attribute, so check the documentation of the version you install.

### Install a chart

```hcl title="ingress.tf"
resource "helm_release" "ingress_nginx" {
  name             = "ingress-nginx"
  repository       = "https://kubernetes.github.io/ingress-nginx"
  chart            = "ingress-nginx"
  version          = "4.11.3"
  namespace        = "ingress-nginx"
  create_namespace = true

  set {
    name  = "controller.replicaCount"
    value = "2"
  }

  set {
    name  = "controller.service.annotations.service\\.beta\\.kubernetes\\.io/aws-load-balancer-type"
    value = "nlb"
  }
}
```

Always pin `version`. The `set` block overrides single values, and the dots in annotation names must be escaped.

### Use a values file

For many settings, a values file is clearer. Use `templatefile` to inject Terraform values:

```hcl title="cert-manager.tf"
resource "helm_release" "cert_manager" {
  name             = "cert-manager"
  repository       = "https://charts.jetstack.io"
  chart            = "cert-manager"
  version          = "v1.16.1"
  namespace        = "cert-manager"
  create_namespace = true

  values = [
    templatefile("${path.module}/values/cert-manager.yaml.tftpl", {
      replicas = var.environment == "pro" ? 2 : 1
    })
  ]
}
```

```yaml title="values/cert-manager.yaml.tftpl"
crds:
  enabled: true
replicaCount: ${replicas}
```

### Sensitive values

```hcl title="secrets.tf"
resource "helm_release" "app" {
  name       = "app"
  chart      = "./charts/app"
  namespace  = "default"

  set_sensitive {
    name  = "database.password"
    value = var.db_password
  }
}
```

`set_sensitive` hides the value in the plan, but it is still stored in the [state](https://www.itwonderlab.com/terraform-state/). See [secrets management](https://www.itwonderlab.com/terraform-secrets-management/): prefer secrets that the application reads from Secrets Manager or an operator.

### Dependencies and CRDs

Charts that define custom resources (cert-manager issuers, Prometheus rules) cannot be planned until their CRDs exist. Install them in separate steps with `depends_on`, or in separate configurations, and use `kubernetes_manifest` or a second Helm chart for the custom resources.

```hcl title="app.tf"
resource "helm_release" "app" {
  # ...
  depends_on = [helm_release.cert_manager, helm_release.ingress_nginx]
}
```

### Upgrades and rollbacks

- Changing `version` or `values` and applying runs a `helm upgrade`.
- `atomic = true` rolls back automatically when the release fails, and `timeout` sets the wait time.
- `helm_release` can leave a release in a failed state. Fix it with `helm rollback` or `tofu apply -replace=helm_release.app`.

### When to use it

Use the Helm provider for the **platform components** that you install once per cluster. For application deployments, a [GitOps tool such as Argo CD](https://www.itwonderlab.com/argocd-gitops-kubernetes/) offers continuous reconciliation and visibility that Terraform runs do not. Related: [Terraform with Kubernetes](https://www.itwonderlab.com/kubernetes-with-opentofu/) and [Helm installation](https://www.itwonderlab.com/install-kubernetes-helm/).
