Kubernetes Deployment with Helm Charts

For the complete documentation index, see llms.txt. For a full content snapshot, see llms-full.txt. Append .md to any kestra.io/docs/* URL for plain Markdown.

Install Kestra in a Kubernetes cluster using a Helm chart.

Prerequisites

  • kubectl — to interact with your cluster
  • Helm — to install and manage charts

Helm charts

Kestra maintains two Helm charts:

  1. kestra — production-ready chart with no bundled dependencies. Requires an external database and object storage.
  2. kestra-starter — bundles PostgreSQL and Versity (S3-compatible storage) for evaluation only. Not suitable for production.

Chart sources:

Chart configuration resources

To understand available configuration options and compare versions:

  • Compare versions: See differences between two Helm chart versions on ArtifactHub using the values comparison modal.
  • Full values reference: Review all available configuration options in the values.yaml file on GitHub.

Starter chart dependencies

The kestra-starter chart installs Versity (object storage) and PostgreSQL (database). These bundled dependencies are not suitable for production.

Enterprise edition

To deploy the Enterprise Edition, authenticate before pulling images:

docker login registry.kestra.io --username $LICENSEID --password $FINGERPRINT

Use:

  • registry.kestra.io/docker/kestra-ee:latest
  • or a pinned version such as registry.kestra.io/docker/kestra-ee:v1.0

Review Enterprise requirements before deploying. Compare editions in Open Source vs Enterprise if you are deciding between versions.

Install Kestra

Add the chart repository:

helm repo add kestra https://helm.kestra.io/
helm repo update

Install the kestra-starter chart:

helm install my-kestra kestra/kestra-starter

This deploys pods for Kestra, PostgreSQL (database), and Versity (storage).

Alternatively, install the kestra production chart:

helm install my-kestra kestra/kestra

This deploys Kestra in standalone mode—all core components run in a single pod.

Access the Kestra UI

To list all pods run:

kubectl get pods -n default -l app.kubernetes.io/name=kestra

If you installed the kestra-starter chart, you will likely see something like:

my-kestra-kestra-starter-xxxxxx-xxxxx Running
my-kestra-postgresql-0 Running
my-kestra-versity-0 Running

The Kestra standalone pod is usually named:

my-kestra-kestra-starter-xxxxx

Export the pod name using the label selector for release my-kestra:

export POD_NAME=$(kubectl get pods \
-l "app.kubernetes.io/name=kestra,app.kubernetes.io/instance=my-kestra,app.kubernetes.io/component=standalone" \
-o jsonpath="{.items[0].metadata.name}")

Verify:

echo $POD_NAME

Port-forward the UI:

kubectl port-forward $POD_NAME 8080:8080

Open http://localhost:8080 in your browser and create your user.

Ingress

To expose Kestra outside the cluster, enable the built-in Ingress resource. Both charts support the same ingress properties — the only difference is where they are placed in values.yaml.

kestra chart

ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0"
hosts:
- host: kestra.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- kestra.example.com
secretName: kestra-tls

kestra-starter chart

The kestra-starter chart passes all values under kestra: through to the main kestra chart, so nest the ingress block accordingly:

kestra:
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "0"
hosts:
- host: kestra.example.com
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- kestra.example.com
secretName: kestra-tls

Apply with:

helm upgrade my-kestra kestra/kestra-starter -f values.yaml

Omit the tls block if TLS is terminated upstream (e.g., at a load balancer). The className and annotations values depend on your ingress controller — replace nginx with the appropriate class for your cluster. For TLS certificate management, see SSL configuration.

Scaling Kestra on Kubernetes

For production deployments, run each Kestra component in its own pod.

Example values.yaml:

deployments:
webserver:
enabled: true
executor:
enabled: true
indexer:
enabled: true
scheduler:
enabled: true
worker:
enabled: true
standalone:
enabled: false

Apply changes:

helm upgrade my-kestra kestra/kestra -f values.yaml

Validate pod layout:

kubectl get pods -l app.kubernetes.io/name=kestra

Configuration

Kestra configuration is provided through Helm values and rendered into ConfigMaps and Secrets.

All Kestra-specific options live under configurations.application. The content of this block is identical to what you would put in a standalone application.yml — the Helm chart passes it through as-is. Every property documented in the configuration section is valid here.

Production example

A typical production values.yaml configures the database, storage backend, and secrets manager together:

configurations:
application:
kestra:
storage:
type: s3
s3:
bucket: my-kestra-bucket
region: us-east-1
secret:
type: aws-secret-manager
aws-secret-manager:
region: us-east-1
queue:
type: postgres
repository:
type: postgres
datasources:
postgres:
url: jdbc:postgresql://postgres:5432/kestra
driverClassName: org.postgresql.Driver
username: kestra
password: ${POSTGRES_PASSWORD}

Inject credentials from a Kubernetes Secret using common.extraEnvFrom:

common:
extraEnvFrom:
- secretRef:
name: postgres-credentials # Secret must contain POSTGRES_PASSWORD

For the full property reference for each area, see:

Minimal example (H2 database for testing only)

configurations:
application:
kestra:
queue:
type: h2
repository:
type: h2
storage:
type: local
local:
base-path: "/app/storage"
datasources:
h2:
url: jdbc:h2:mem:public;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
username: kestra
password: kestra
driverClassName: org.h2.Driver

Using secrets

Secrets can be mounted into Kestra through the secrets section and referenced via manifests.

Example: enabling Kafka using a Secret

configurations:
application:
kestra:
queue:
type: kafka
secrets:
- name: kafka-server
key: kafka.yml

Secret manifest:

extraManifests:
- apiVersion: v1
kind: Secret
metadata:
name: kafka-server
stringData:
kafka.yml: |
kestra:
kafka:
client:
properties:
bootstrap.servers: "localhost:9092"

Environment variables

Use extraEnv or extraEnvFrom to load values from existing Secrets or ConfigMaps.

Example:

common:
extraEnvFrom:
- secretRef:
name: basic-auth-secret

Secret manifest:

extraManifests:
- apiVersion: v1
kind: Secret
metadata:
name: basic-auth-secret
stringData:
basic-auth.yml: |
kestra:
server:
basic-auth:
enabled: true
username: admin@localhost.com
password: ChangeMe1234!

Docker-in-Docker (DinD)

Kestra workers support rootless Docker-in-Docker by default. Some clusters restrict this.

On Google Kubernetes Engine (GKE), using a node pool based on UBUNTU_CONTAINERD works well with rootless Docker DinD.

Disable rootless mode

Some clusters only support a root version of DinD. To enable insecure (privileged) mode instead, use the insecure mode Helm values:

dind:
# -- Enable Docker-in-Docker (dind) sidecar.
# @section -- kestra dind
enabled: true
# -- Dind mode (rootless or insecure).
# @section -- kestra dind
mode: 'rootless'
base:
# -- Rootless dind configuration.
# @section -- kestra dind rootless
rootless:
image:
repository: docker
pullPolicy: IfNotPresent
tag: dind-rootless
securityContext:
privileged: true
runAsUser: 1000
runAsGroup: 1000
args:
- --log-level=fatal
- --group=1000
# -- Insecure dind configuration (privileged).
# @section -- kestra dind insecure
insecure:
image:
repository: docker
pullPolicy: IfNotPresent
tag: dind-rootless
securityContext:
privileged: true
runAsUser: 0
runAsGroup: 0
allowPrivilegeEscalation: true
capabilities:
add:
- SYS_ADMIN
- NET_ADMIN
- DAC_OVERRIDE
- SETUID
- SETGID
args:
- '--log-level=fatal'

Troubleshooting DinD

If you encounter errors like the following on some Kubernetes deployments:

Device "ip_tables" does not exist.
ip_tables 24576 4 iptable_raw,iptable_mangle,iptable_nat,iptable_filter
modprobe: can't change directory to '/lib/modules': No such file or directory
error: attempting to run rootless dockerd but need 'kernel.unprivileged_userns_clone' (/proc/sys/kernel/unprivileged_userns_clone) set to 1

Attach to the DinD container to inspect logs:

docker run -it --privileged docker:dind sh
docker logs <container-id>
docker inspect <container-id>

Disable DinD and use Kubernetes task runner

To avoid using root to spin up containers via DinD, disable DinD by setting the following Helm chart values:

dind:
enabled: false

Use the Kubernetes task runner as the default method for running script tasks:

pluginDefaults:
- type: io.kestra.plugin.scripts
forced: true
values:
taskRunner:
type: io.kestra.plugin.ee.kubernetes.runner.Kubernetes
# ... your Kubernetes runner configuration

Was this page helpful?