Kubernetes Deployment with Helm Charts
For the complete documentation index, see llms.txt. For a full content snapshot, see llms-full.txt. Append.mdto anykestra.io/docs/*URL for plain Markdown.
Install Kestra in a Kubernetes cluster using a Helm chart.
Prerequisites
Helm charts
Kestra maintains two Helm charts:
kestra— production-ready chart with no bundled dependencies. Requires an external database and object storage.kestra-starter— bundles PostgreSQL and Versity (S3-compatible storage) for evaluation only. Not suitable for production.
Chart sources:
- Repository: helm.kestra.io
- Source code: kestra helm chart
- ArtifactHub: kestra · kestra-starter
All default image tags are listed in the Docker installation guide.
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 $FINGERPRINTUse:
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.
If you use ArgoCD, see Deploy Kestra with ArgoCD using a Wrapper Chart for the recommended GitOps deployment pattern.
Install Kestra
Add the chart repository:
helm repo add kestra https://helm.kestra.io/helm repo updateInstall the kestra-starter chart:
helm install my-kestra kestra/kestra-starterThis deploys pods for Kestra, PostgreSQL (database), and Versity (storage).
Alternatively, install the kestra production chart:
helm install my-kestra kestra/kestraThis deploys Kestra in standalone mode—all core components run in a single pod.
The kestra chart does not include PostgreSQL or object storage. Configure these before production deployment.
Access the Kestra UI
To list all pods run:
kubectl get pods -n default -l app.kubernetes.io/name=kestraIf you installed the kestra-starter chart, you will likely see something like:
my-kestra-kestra-starter-xxxxxx-xxxxx Runningmy-kestra-postgresql-0 Runningmy-kestra-versity-0 RunningThe Kestra standalone pod is usually named:
my-kestra-kestra-starter-xxxxxExport 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_NAMEPort-forward the UI:
kubectl port-forward $POD_NAME 8080:8080Open 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-tlskestra-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-tlsApply with:
helm upgrade my-kestra kestra/kestra-starter -f values.yamlOmit 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: falseApply changes:
helm upgrade my-kestra kestra/kestra -f values.yamlValidate pod layout:
kubectl get pods -l app.kubernetes.io/name=kestraConfiguration
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_PASSWORDFor the full property reference for each area, see:
- Runtime and Storage — storage backends (S3, GCS, Azure, and more) and datasource configuration
- Security and Secrets — secrets backends
- Configuration basics — queue and repository type selection, environment variables, property naming, and override patterns
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.DriverUsing 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.ymlSecret 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-secretSecret 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_filtermodprobe: can't change directory to '/lib/modules': No such file or directoryerror: attempting to run rootless dockerd but need 'kernel.unprivileged_userns_clone' (/proc/sys/kernel/unprivileged_userns_clone) set to 1Attach to the DinD container to inspect logs:
docker run -it --privileged docker:dind shdocker 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: falseUse 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 configurationWas this page helpful?