Skip to main content

Command Palette

Search for a command to run...

Article 2: Installing Kestra: Your First Steps to Powerful Orchestration.

Updated

Complete Setup Guide for Local, Docker, and Cloud Deployments.

Introduction: Why Installation Matters

Think of Kestra as the conductor of your data orchestra. Just as a great conductor needs the right stage setup, Kestra needs proper installation to perform at its best. In this guide, we'll walk through every installation option—from a quick local setup to enterprise-grade deployments.

Quick Decision Guide:

  • Just exploring? → Use Docker standalone (5 minutes)

  • Developing workflows? → Use Docker Compose (10 minutes)

  • Production deployment? → Use Kubernetes/Helm (30 minutes)

  • Enterprise scale? → Use Terraform on cloud (60 minutes)


Option 1: Local Development (Fastest Path)

Prerequisites Checklist

Before we begin, ensure you have:

  • ✅ Docker installed (Get Docker)

  • ✅ 2GB free RAM

  • ✅ 10GB free disk space

  • ✅ Internet connection

Method A: Quick Start with Docker (5 Minutes)

# Run this single command
docker run --rm \
  -p 8080:8080 \
  -v $(pwd)/flows:/app/flows \
  -v $(pwd)/storage:/app/storage \
  kestra/kestra:latest \
  standalone

# Or for Windows PowerShell
docker run --rm `
  -p 8080:8080 `
  -v ${PWD}/flows:/app/flows `
  -v ${PWD}/storage:/app/storage `
  kestra/kestra:latest `
  standalone

What this does:

  • Starts Kestra with embedded databases

  • Maps local directories for flows and storage

  • Opens port 8080 for the web UI

  • Uses temporary containers (removed on stop)

Access the UI:

  1. Open http://localhost:8080

  2. Default credentials: admin / password (change immediately!)

Create a docker-compose.yml file:

version: '3.8'

services:
  kestra:
    image: kestra/kestra:latest
    container_name: kestra-server
    ports:
      - "8080:8080"
      - "8081:8081"  # For metrics if needed
    environment:
      - KESTRA_CONFIGURATION= |
          kestra:
            server:
              basic-auth:
                enabled: true
                username: admin
                password: ${KESTRA_PASSWORD:-changeme}
            repository:
              type: postgres
            storage:
              type: local
            queue:
              type: postgres
    volumes:
      - ./flows:/app/flows      # Your workflow definitions
      - ./plugins:/app/plugins  # Custom plugins
      - ./logs:/app/logs        # Execution logs
      - ./storage:/app/storage  # File storage
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped

  postgres:
    image: postgres:15-alpine
    container_name: kestra-postgres
    environment:
      POSTGRES_DB: kestra
      POSTGRES_USER: kestra
      POSTGRES_PASSWORD: kestra
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init-db:/docker-entrypoint-initdb.d  # Optional: initial scripts
    ports:
      - "5432:5432"  # Optional: expose for external tools
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U kestra"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  postgres_data:

Start everything with one command:

# Create necessary directories
mkdir -p flows plugins logs storage init-db

# Start all services
docker-compose up -d

# Check status
docker-compose ps

# View logs
docker-compose logs -f kestra

What you get:

  • ✅ Persistent database (PostgreSQL)

  • ✅ File storage that survives restarts

  • ✅ Proper service health checks

  • ✅ Easy log viewing

  • ✅ Production-like setup locally


Option 2: Production-Ready Kubernetes Setup

Prerequisites:

  • Kubernetes cluster (Minikube, Docker Desktop, EKS, AKS, GKE)

  • Helm 3+

  • kubectl configured

Step 1: Create Namespace and Secrets

# Create namespace
kubectl create namespace kestra

# Create a secret for credentials
kubectl create secret generic kestra-secrets \
  --namespace kestra \
  --from-literal=postgres-password='YourSecurePassword123!' \
  --from-literal=admin-password='AnotherSecurePassword456!'

# Create storage class (if needed)
cat <<EOF | kubectl apply -f -
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: kestra-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
EOF

Step 2: Deploy with Helm

# Add Kestra Helm repository
helm repo add kestra https://kestra.io/helm
helm repo update

# Install Kestra with custom values
cat > kestra-values.yaml << EOF
# kestra-values.yaml
global:
  storageClass: kestra-storage

server:
  replicaCount: 2
  autoscaling:
    enabled: true
    minReplicas: 2
    maxReplicas: 10
  resources:
    requests:
      memory: "1Gi"
      cpu: "500m"
    limits:
      memory: "2Gi"
      cpu: "1000m"
  extraEnv:
    - name: JAVA_OPTS
      value: "-Xmx1g -Xms512m"

postgresql:
  enabled: true
  auth:
    database: kestra
    username: kestra
    existingSecret: kestra-secrets
    secretKeys:
      adminPasswordKey: postgres-password
  persistence:
    size: 50Gi
    storageClass: kestra-storage

ingress:
  enabled: true
  className: nginx
  hosts:
    - host: kestra.yourcompany.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: kestra-tls
      hosts:
        - kestra.yourcompany.com

redis:
  enabled: true
  architecture: standalone
  auth:
    existingSecret: kestra-secrets
    secretKeys:
      redis-passwordKey: redis-password

elasticsearch:
  enabled: true  # For enhanced logging and search
EOF

# Install the chart
helm install kestra kestra/kestra \
  --namespace kestra \
  --values kestra-values.yaml \
  --wait

# Check deployment status
kubectl get all -n kestra

Step 3: Configure Ingress and TLS

# If using cert-manager for TLS
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: kestra-tls
  namespace: kestra
spec:
  secretName: kestra-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - kestra.yourcompany.com
EOF

Step 4: Verify Deployment

# Check pods
kubectl get pods -n kestra -w

# Check services
kubectl get svc -n kestra

# View logs
kubectl logs -n kestra deployment/kestra-server -f

# Port-forward for local access (if needed)
kubectl port-forward -n kestra svc/kestra-server 8080:80

Option 3: Cloud-Specific Deployments

AWS ECS Setup (Fargate)

# ecs-task-definition.json
{
  "family": "kestra-server",
  "networkMode": "awsvpc",
  "taskRoleArn": "arn:aws:iam::123456789012:role/ecsTaskRole",
  "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
  "cpu": "2048",
  "memory": "4096",
  "containerDefinitions": [
    {
      "name": "kestra",
      "image": "kestra/kestra:latest",
      "portMappings": [
        {
          "containerPort": 8080,
          "protocol": "tcp"
        }
      ],
      "environment": [
        {
          "name": "KESTRA_CONFIGURATION",
          "value": "kestra.repository.type=postgres\nkestra.storage.type=s3"
        }
      ],
      "secrets": [
        {
          "name": "DB_PASSWORD",
          "valueFrom": "arn:aws:secretsmanager:region:account:secret:db_password"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/kestra",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ]
}

Deploy with AWS CLI:

# Register task definition
aws ecs register-task-definition \
  --cli-input-json file://ecs-task-definition.json

# Create service
aws ecs create-service \
  --cluster production \
  --service-name kestra \
  --task-definition kestra-server:1 \
  --desired-count 3 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[subnet-xxx,subnet-yyy],securityGroups=[sg-zzz],assignPublicIp=ENABLED}"

Google Cloud Run (Serverless)

# Deploy to Cloud Run
gcloud run deploy kestra \
  --image=kestra/kestra:latest \
  --platform=managed \
  --region=us-central1 \
  --allow-unauthenticated \
  --memory=2Gi \
  --cpu=2 \
  --set-env-vars="KESTRA_CONFIGURATION=..."

# Set up Cloud SQL connection
gcloud run services update kestra \
  --add-cloudsql-instances=PROJECT:REGION:INSTANCE \
  --set-env-vars="DB_HOST=/cloudsql/PROJECT:REGION:INSTANCE"

Azure Container Instances

# Create container instance
az container create \
  --resource-group kestra-rg \
  --name kestra-server \
  --image kestra/kestra:latest \
  --ports 8080 \
  --memory 4 \
  --cpu 2 \
  --environment-variables \
    KESTRA_CONFIGURATION='kestra.repository.type=postgres' \
  --dns-name-label kestra-unique \
  --azure-file-volume-share-name kestra-data \
  --azure-file-volume-account-name mystorageaccount \
  --azure-file-volume-account-key $STORAGE_KEY

Option 4: Bare Metal / VM Installation

For environments where containers aren't an option:

Step 1: Install Java and Dependencies

# Ubuntu/Debian
sudo apt update
sudo apt install -y openjdk-17-jre-headless postgresql-15 redis-server

# CentOS/RHEL
sudo yum install -y java-17-openjdk postgresql15-server redis

# macOS
brew install openjdk@17 postgresql@15 redis

Step 2: Configure Database

# Initialize PostgreSQL
sudo postgresql-setup initdb
sudo systemctl start postgresql
sudo systemctl enable postgresql

# Create Kestra database and user
sudo -u postgres psql << EOF
CREATE DATABASE kestra;
CREATE USER kestra WITH PASSWORD 'secure_password';
GRANT ALL PRIVILEGES ON DATABASE kestra TO kestra;
ALTER DATABASE kestra SET timezone TO 'UTC';
EOF

# Update pg_hba.conf for password auth
echo "host    kestra          kestra          127.0.0.1/32            md5" | sudo tee -a /etc/postgresql/15/main/pg_hba.conf
sudo systemctl restart postgresql

Step 3: Install Kestra

# Download Kestra
wget https://github.com/kestra-io/kestra/releases/latest/download/kestra-standalone.jar -O /opt/kestra/kestra.jar

# Create systemd service
cat << EOF | sudo tee /etc/systemd/system/kestra.service
[Unit]
Description=Kestra Workflow Orchestrator
After=network.target postgresql.service redis.service

[Service]
Type=simple
User=kestra
Group=kestra
WorkingDirectory=/opt/kestra
ExecStart=/usr/bin/java -Xmx2g -jar /opt/kestra/kestra.jar server standalone

# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/var/lib/kestra

[Install]
WantedBy=multi-user.target
EOF

# Create user and directories
sudo useradd -r -s /bin/false kestra
sudo mkdir -p /opt/kestra /var/lib/kestra/{flows,storage,logs,plugins}
sudo chown -R kestra:kestra /opt/kestra /var/lib/kestra

# Start service
sudo systemctl daemon-reload
sudo systemctl enable kestra
sudo systemctl start kestra
sudo systemctl status kestra

Verification and Testing

Health Check

# Using curl
curl http://localhost:8080/api/v1/health

# Expected response
{
  "status": "UP",
  "components": {
    "database": {
      "status": "UP"
    },
    "storage": {
      "status": "UP"
    }
  }
}

# Using API
curl -X GET "http://localhost:8080/api/v1/configs" \
  -H "Authorization: Basic $(echo -n 'admin:password' | base64)"

Create Your First Flow Programmatically

# test_kestra.py
import requests
import json
import yaml

# Configure
BASE_URL = "http://localhost:8080"
USERNAME = "admin"
PASSWORD = "password"
NAMESPACE = "demo"

# Create namespace
auth = (USERNAME, PASSWORD)
headers = {"Content-Type": "application/json"}

namespace_payload = {
    "namespace": NAMESPACE,
    "description": "Demo namespace"
}

response = requests.post(
    f"{BASE_URL}/api/v1/namespaces",
    json=namespace_payload,
    auth=auth,
    headers=headers
)

# Create a simple flow
flow_yaml = """
id: hello-world
namespace: demo
description: My first Kestra flow

tasks:
  - id: hello
    type: io.kestra.plugin.core.log.Log
    message: "Hello, Kestra! 🎉"

  - id: get-time
    type: io.kestra.plugin.core.debug.Return
    format: "Flow started at {{ execution.startDate }}"

triggers:
  - id: schedule
    type: io.kestra.plugin.core.trigger.Schedule
    cron: "*/5 * * * *"  # Every 5 minutes
"""

# Create flow
response = requests.post(
    f"{BASE_URL}/api/v1/flows/{NAMESPACE}",
    data=flow_yaml,
    auth=auth,
    headers={"Content-Type": "application/yaml"}
)

# Execute flow
response = requests.post(
    f"{BASE_URL}/api/v1/executions/trigger/{NAMESPACE}/hello-world",
    auth=auth,
    headers=headers
)

print("Flow created and triggered successfully!")

Test with Kestra CLI

bash

# Install CLI
pip install kestra

# Configure
kestra config set-endpoint http://localhost:8080
kestra config set-username admin
kestra config set-password password

# Test commands
kestra namespace list
kestra flow list --namespace demo
kestra flow execute --namespace demo --flow hello-world
kestra execution list --namespace demo

Configuration Deep Dive

Configuration File Structure

Create kestra.yml for custom configurations:

# kestra.yml
kestra:
  server:
    basic-auth:
      enabled: true
      username: admin
      password: ${KESTRA_PASSWORD:?Password required}

    cors:
      enabled: true
      allowed-origins: ["https://*.yourcompany.com"]

    metrics:
      enabled: true
      port: 8081
      path: /metrics

  repository:
    type: postgres
    postgres:
      host: ${DB_HOST:localhost}
      port: ${DB_PORT:5432}
      database: kestra
      username: kestra
      password: ${DB_PASSWORD:?DB password required}
      pool-size: 10

  storage:
    type: s3  # or local, gcs, azure
    s3:
      region: ${AWS_REGION:us-east-1}
      bucket: kestra-storage
      endpoint: ${AWS_ENDPOINT:}  # For MinIO or other S3-compatible
      access-key-id: ${AWS_ACCESS_KEY_ID:}
      secret-access-key: ${AWS_SECRET_ACCESS_KEY:}

  queue:
    type: postgres  # or redis, kafka

  cache:
    type: redis
    redis:
      host: ${REDIS_HOST:localhost}
      port: ${REDIS_PORT:6379}
      password: ${REDIS_PASSWORD:}

  plugins:
    location: /app/plugins
    repositories:
      - id: kestra-io
        url: https://repo.kestra.io/repository/maven-public/

  tasks:
    default-retry:
      type: exponential
      max-attempt: 3
      delay: PT5S

  security:
    secret-key: ${SECRET_KEY:?Secret key required for encryption}
    encryption:
      enabled: true

Environment Variables

# .env file example
KESTRA_PASSWORD=SuperSecurePassword123!
DB_HOST=postgres
DB_PORT=5432
DB_PASSWORD=db_password_456
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
REDIS_HOST=redis
REDIS_PASSWORD=redis_pass_789
SECRET_KEY=$(openssl rand -hex 32)

Run with Custom Config

# Docker with custom config
docker run --rm \
  -p 8080:8080 \
  -v $(pwd)/kestra.yml:/app/kestra.yml \
  -v $(pwd)/.env:/app/.env \
  --env-file .env \
  kestra/kestra:latest \
  server standalone

# Or with environment variables
docker run --rm \
  -p 8080:8080 \
  -e KESTRA_PASSWORD=password \
  -e DB_HOST=postgres \
  kestra/kestra:latest \
  server standalone

Troubleshooting Guide

Common Issues and Solutions

1. Port Already in Use

# Find what's using port 8080
sudo lsof -i :8080

# Kill the process
sudo kill -9 <PID>

# Or change Kestra port
docker run --rm -p 9090:8080 kestra/kestra:latest

2. Database Connection Issues

# Test database connection
docker exec -it kestra-postgres psql -U kestra -d kestra

# Reset database (development only)
docker-compose down -v
docker-compose up -d

3. Permission Denied for Storage

# Fix volume permissions
sudo chown -R 1000:1000 ./flows ./storage ./logs

# Or run as current user
docker run --rm \
  -u $(id -u):$(id -g) \
  -v $(pwd)/flows:/app/flows \
  kestra/kestra:latest

4. Out of Memory Errors

# Increase memory in docker-compose
services:
  kestra:
    deploy:
      resources:
        limits:
          memory: 2G
        reservations:
          memory: 1G

5. Flow Not Appearing

# Check if flows are being loaded
curl http://localhost:8080/api/v1/flows

# Check logs for errors
docker-compose logs kestra | grep -i flow

# Verify directory mapping
docker exec -it kestra-server ls -la /app/flows

Debug Commands

# Get detailed system info
curl http://localhost:8080/api/v1/configs/debug

# Check plugin availability
curl http://localhost:8080/api/v1/plugins

# View server metrics
curl http://localhost:8080/api/v1/metrics

# Check queue status
curl http://localhost:8080/api/v1/queues

Post-Installation Checklist

✅ Security Hardening

# 1. Change default password
curl -X PUT "http://localhost:8080/api/v1/users/admin" \
  -H "Authorization: Basic $(echo -n 'admin:password' | base64)" \
  -H "Content-Type: application/json" \
  -d '{"password": "NewSecurePassword123!"}'

# 2. Enable HTTPS
# 3. Set up firewall rules
# 4. Configure audit logging

✅ Backup Strategy

# Backup flows
cp -r ./flows ./backups/flows-$(date +%Y%m%d)

# Backup database
docker exec kestra-postgres pg_dump -U kestra kestra > backup-$(date +%Y%m%d).sql

# Backup configuration
cp kestra.yml kestra.yml.backup-$(date +%Y%m%d)

✅ Monitoring Setup

# Install monitoring stack
docker-compose -f monitoring/docker-compose.yml up -d

# Configure Prometheus
echo "
  - job_name: 'kestra'
    static_configs:
      - targets: ['kestra:8081']
" >> prometheus/prometheus.yml

Production Deployment Checklist

Before Going Live:

  • Performance Testing: Load test with 100+ concurrent flows

  • Disaster Recovery: Test backup/restore procedures

  • High Availability: Ensure multiple replicas are running

  • Monitoring: Set up alerts for critical metrics

  • Security Audit: Review access controls and encryption

  • Documentation: Document deployment procedures

  • Rollback Plan: Test downgrade procedure

  • Load Balancer: Configure proper load balancing

  • DNS Setup: Configure proper DNS records

  • SSL/TLS: Set up proper certificates

Critical Metrics to Monitor:

  • Flow execution success rate (>99%)

  • Average execution time

  • Queue backlog size

  • Database connection pool usage

  • Memory and CPU utilization

  • Storage usage

  • API response times


Next Steps: What to Build First

Now that Kestra is installed, here are some practical projects to start with:

Project 1: Data Pipeline Template

# Create project structure
mkdir -p flows/{extract,transform,load}
mkdir -p scripts/{python,sql}
mkdir -p config

# Create your first real flow
cat > flows/extract/daily-sales.yml << 'EOF'
id: daily-sales-extract
namespace: sales.pipeline
description: Extract daily sales data

tasks:
  - id: extract-api
    type: io.kestra.plugin.core.http.Download
    uri: "https://api.company.com/sales/{{ execution.startDate | date('yyyy-MM-dd') }}"
    outputFile: "sales_{{ execution.startDate | date('yyyy-MM-dd') }}.json"
EOF

Project 2: Monitoring Dashboard

  1. Deploy Grafana alongside Kestra

  2. Import Kestra dashboard templates

  3. Set up alerts for failed executions

  4. Create custom metrics dashboards

Project 3: CI/CD Pipeline

# .github/workflows/kestra-deploy.yml
name: Deploy Kestra Flows
on:
  push:
    paths:
      - 'flows/**'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Validate flows
        run: |
          docker run --rm \
            -v $(pwd)/flows:/flows \
            kestra/kestra:latest \
            flow validate /flows

      - name: Deploy to Kestra
        run: |
          curl -X POST "https://kestra.company.com/api/v1/flows/batch" \
            -H "Authorization: Bearer ${{ secrets.KESTRA_TOKEN }}" \
            -F "files=@flows/sales-pipeline.yml" \
            -F "namespace=sales"

Community and Support

Getting Help:

Contributing:

# Clone the repository
git clone https://github.com/kestra-io/kestra.git
cd kestra

# Build from source
./gradlew build

# Run tests
./gradlew test

# Submit pull request

Conclusion

You now have Kestra running in your chosen environment! Whether you opted for the quick Docker setup or a full Kubernetes deployment, you're ready to start building powerful data workflows.

Key Takeaways:

  1. Start simple: Use Docker standalone for exploration

  2. Scale gradually: Move to Docker Compose, then Kubernetes

  3. Configure properly: Use environment variables and config files

  4. Monitor everything: Set up metrics and alerts from day one

  5. Join the community: The Kestra community is active and helpful

What's Next?

In the next article, we'll dive into Building Your First ETL Pipeline. We'll cover:

  • Extracting data from multiple sources

  • Transforming with Python and SQL

  • Loading to databases and data warehouses

  • Error handling and monitoring

  • Best practices for production pipelines

Before the next article, try:

  1. Create a namespace for your project

  2. Build a simple flow that downloads and processes a public dataset

  3. Explore the web UI and understand the execution view

  4. Join the Slack community and introduce yourself


Remember: The goal of installation isn't just to get software running—it's to create a foundation that's secure, scalable, and maintainable. Take the time to do it right, and your future self will thank you.

Happy orchestrating! 🚀

More from this blog

T

techwasti

276 posts

TechWasti is a community where we are sharing thoughts, concepts, ideas, and codes.