Helm

Helm

Certified

Deploy and manage Kubernetes applications from Helm charts.

Wraps the Helm CLI as orchestrated tasks — upgrade, uninstall, rollback, status and template — so a Kubernetes deployment becomes one step in a broader workflow, and each deployed release is registered as a queryable, lineage-tracked Asset.

Deploy and manage Kubernetes applications from Helm charts.

This plugin wraps the helm CLI as orchestrated tasks, so a deployment becomes one step in a broader workflow — build an image, run migrations, deploy the chart, smoke-test, notify — rather than an opaque shell-out. On the Enterprise Edition each deployed release and the cluster resources it manages are also registered as Assets, turning a fire-and-forget helm upgrade into a queryable deployment record.

Tasks

Task Purpose
Upgrade helm upgrade --install — creates the release when absent, updates it when present
Uninstall Removes a release and the resources it manages
Rollback Reverts a release to an earlier revision
Status Reads current release state without changing anything
Template Renders a chart to manifests for review or diffing; never contacts the cluster

Requirements

Helm 4. The default image is alpine/helm: 4.3.0. A Helm 3 image will reject flags this plugin relies on, including --rollback-on-failure and --force-replace, which replaced --atomic and --force in Helm 4.

The helm binary runs inside a container started by the task runner (Docker by default), so the Kestra worker needs access to a container runtime. Nothing needs to be installed on the worker itself.

Connecting to a cluster

Use connection, which is the same model as the Kubernetes plugin:

- id: deploy
  type: io.kestra.plugin.helm.Upgrade
  releaseName: nginx
  namespace: web
  chart: 
    repository: https://charts.bitnami.com/bitnami
    name: nginx
    version: "15.4.2"
  connection: 
    masterUrl: https://prod-eu.k8s.example.com
    caCertData: "{{ secret('PROD_EU_CA') }}"
    oauthToken: "{{ secret('PROD_EU_TOKEN') }}"

connection supports an ambient service account (inheritClusterConfig: true), client certificates, a bearer token, basic auth, and short-lived cloud provider tokens through oauthTokenProvider. Alternatively pass a complete kubeconfig with the kubeconfig property, and select a context with kubeContext. connection and kubeconfig are mutually exclusive.

The plugin renders whatever you supply into a kubeconfig inside the task's working directory and points Helm at it, so credentials never appear on the command line.

Where charts come from

A Helm repository, where the chart name and repository stay separate:

chart: 
  repository: https://charts.bitnami.com/bitnami
  name: nginx
  version: "15.4.2"

An OCI registry, recognised by the oci:// prefix:

chart: 
  repository: oci://ghcr.io/stefanprodan/charts
  name: podinfo
  version: "6.15.0"

Or a local path, relative to the working directory — useful for a chart produced by an earlier task:

chart: 
  path: charts/nginx

Charts held in Git

There is no git block on chart. Clone the repository with the Git plugin inside a WorkingDirectory and point chart.path at the checkout, which keeps Git authentication in one place rather than duplicating it here:

- id: deploy
  type: io.kestra.plugin.core.flow.WorkingDirectory
  tasks: 
    - id: clone
      type: io.kestra.plugin.git.Clone
      url: https://github.com/acme/k8s-config
      branch: main
      username: "{{ secret('GH_USER') }}"
      password: "{{ secret('GH_PAT') }}"

    - id: upgrade
      type: io.kestra.plugin.helm.Upgrade
      releaseName: nginx
      namespace: web
      chart: 
        path: k8s-config/charts/nginx
      valuesFrom: 
        - k8s-config/prod/nginx/values.yaml

Values precedence

valuesFrom files are applied in order, then inline values last. Later wins, so inline values override everything:

valuesFrom: 
  - base.yaml
  - prod.yaml
values: 
  image: 
    tag: "{{ inputs.image_tag }}"

Waiting for a deployment to be ready

Helm returns as soon as the manifests are accepted unless told otherwise, so a following task can run against pods that are not yet serving. Set wait: WATCHER when that matters:

wait: WATCHER
timeout: PT10M
rollbackOnFailure: true

rollbackOnFailure reverts the release if the upgrade fails. The task still fails, so an errors block that also runs Rollback would move the revision a second time — pick one.

Reviewing a change before applying it

Template renders locally and never contacts the cluster. Upgrade with dryRun: SERVER goes further and validates the rendered manifests against the live API server, which catches problems Template cannot. Neither emits Assets.

Both return manifest as a URI in Kestra's internal storage, so successive executions can be diffed against each other.

Assets

Upgrade, Rollback, Status, and Uninstall register the release and the resources it manages as Assets, with the chart and values files as inputs. Uninstall soft-deletes them rather than leaving them orphaned. HelmRelease and KubernetesResource aren't typed asset classes yet, so these are io.kestra.core.models.assets.Custom assets, with their type string (io.kestra.plugin.ee.assets.HelmRelease / io.kestra.plugin.ee.assets.KubernetesResource) chosen to match the typed classes those would become if core-ee adds them later — the type string stays identical either way, so nothing in the catalog needs to change on that swap.

On the Enterprise Edition, add assets: { enableAuto: true } to the task, or nothing is registered:

- id: deploy
  type: io.kestra.plugin.helm.Upgrade
  releaseName: nginx
  namespace: web
  chart: 
    repository: https://charts.bitnami.com/bitnami
    name: nginx
    version: "15.4.2"
  assets: 
    enableAuto: true

enableAuto is the switch that controls whether emitted assets are captured at all, not only whether dynamically-referenced assets get auto-detected — a task without this block runs and returns successfully, and this plugin cannot default it to true for you, because a non-null assets block on a task fails flow validation on the open-source edition. Without it, the task still calls the asset emission API internally, but nothing reaches the catalog.

Set cluster, region, and environment to label them meaningfully — cluster falls back to the API server host, and the other two are not inferred. Use resourceKinds to narrow which kinds are registered.

Set cluster explicitly when you manage more than one cluster. The fallback is the API server hostname, so two clusters reached through the same load balancer or ingress hostname produce the same Asset identity, and a release deployed to each collapses into a single catalog entry. An explicit name per cluster keeps them distinct:

- id: deploy_eu
  type: io.kestra.plugin.helm.Upgrade
  cluster: prod-eu
  region: europe-west1
  environment: production
  # ...

- id: deploy_us
  type: io.kestra.plugin.helm.Upgrade
  cluster: prod-us
  region: us-east1
  environment: production
  # ...

Assets are an Enterprise Edition feature. On the open-source edition the tasks run normally and emission is skipped. Asset emission failures never fail an otherwise successful deploy; set assetFailureBehavior: FAIL to change that.

Passing a chart inline with inputFiles

Chart templates use Go templating, which collides with Kestra's own {{ }} expressions. Wrap the content in {% verbatim %} … {% endverbatim %} or Kestra will try to evaluate it:

inputFiles: 
  hello/templates/configmap.yaml: |
    {% verbatim %}apiVersion: v1
    kind: ConfigMap
    metadata: 
      name: {{ .Release.Name }}-config{% endverbatim %}

The tag is verbatim; the Jinja spelling fails with Unexpected tag name "raw".

Troubleshooting

A failed Upgrade does not always mean nothing changed. Helm applies the release before this plugin reads the result back, so a failure during that read leaves the deploy in place. Check with Status before retrying.

The Helm container cannot reach the cluster. It runs in its own container, so an API server address of 127.0.0.1 refers to that container rather than your host. Use an address reachable from inside it, and where the cluster runs in Docker, put the container on the same network with the task runner's networkMode.

No Assets show up on the Enterprise Edition even though the task succeeded. Add assets: { enableAuto: true } to the task — see Assets above.