How to Self Host Kestra on Cloud VPS

How to Self Host Kestra on Cloud VPS

What You’ll Need

  • Hetzner VPS or DigitalOcean (Ubuntu 22.04 LTS, minimum 4GB RAM and 2 vCPUs recommended)
  • Contabo VPS as a high-memory alternative host
  • Namecheap to purchase and manage your domain name and DNS records
  • Basic familiarity with SSH, Docker, and command-line system administration

Table of Contents

Server Provisioning and Initial Hardening

Kestra is an open source event-driven orchestrator built to manage complex data pipelines, automation scripts, and scheduled jobs. While lightweight tools like Airflow require complex celery workers and queue infrastructure, Kestra consolidates its scheduler, worker, and executor logic into a streamlined Java runtime.

To host Kestra reliably, start by spinning up an Ubuntu 22.04 instance on Hetzner VPS or DigitalOcean. A server with 4GB RAM gives you plenty of headroom to run the Kestra engine alongside PostgreSQL and a Traefik reverse proxy.

Once your server instance is active, connect to your server over SSH as the root user:

ssh root@your_server_ip

First, update system packages to their latest patches:

sudo apt update && sudo apt upgrade -y

Next, create a dedicated non-root user with sudo permissions for running services:

adduser deploy
usermod -aG sudo deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy

Configure Uncomplicated Firewall (UFW) to allow SSH connections, HTTP traffic, and HTTPS traffic while blocking all other incoming ports:

ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

Before running production orchestrators on open networks, ensure you follow our guide on Hardening SSH Server Security on Ubuntu VPS to disable root logins, alter key parameters, and prevent unauthorized terminal access.

💡 Fast-Track Your Project: Don’t want to configure this yourself? I build custom n8n pipelines and bots. Message me with code SYS3-HUGO.

Installing Docker and Configuring Resource Limits

Kestra runs seamlessly in containerized environments. We will install Docker Engine and the Docker Compose plugin directly from the official Docker repository to ensure access to modern features and stability patches.

Switch to your newly created non-root deploy user:

su - deploy

Execute the following terminal commands to install the required dependencies and import Docker GPG keys:

sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg lsb-release

sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

Grant your deploy user access to the Docker daemon group so you can execute docker commands without sudo:

sudo usermod -aG docker deploy
newgrp docker

When running complex automation workflows that execute Python, Node, or custom shell operations within container instances, orchestrators can quickly consume unallocated CPU cores and host RAM. Take a look at our complete guide on How to Restrict Docker Container Resource Usage to limit container resources and protect your host environment from memory leaks or CPU throttling.

Configuring PostgreSQL and Kestra Backend

Kestra supports multiple storage and queue engines. For single instance self-hosted setups, PostgreSQL acts as both the state storage layer and the queue mechanism through database pooling.

Create a working directory on your host system to store Kestra runtime configurations, dynamic logs, and database mounts:

mkdir -p ~/kestra-stack/config
mkdir -p ~/kestra-stack/postgres_data
mkdir -p ~/kestra-stack/traefik
cd ~/kestra-stack

Now, construct the explicit Kestra application configuration file named config/application.yml. This configuration defines database connectivity parameters, internal queue handlers, security configuration options, and plugin execution behaviors:

kestra:
  repository:
    type: postgres
  queue:
    type: postgres
  storage:
    type: local
    local:
      base-path: "/tmp/kestra-workdir/storage"

datasources:
  postgres:
    url: jdbc:postgresql://postgres:5432/kestra
    driver-class-name: org.postgresql.Driver
    username: kestra_user
    password: SuperSecretPassword123!
    maximum-pool-size: 20
    minimum-idle: 5

server:
  basic-auth:
    enabled: true
    username: admin@example.com
    password: StrongAdminPassword456!

logger:
  levels:
    org.kestra: INFO

If you plan on running hundreds of simultaneous workflow tasks hitting database connections concurrently, consider reading Configuring PgBouncer Connection Pooling for PostgreSQL to prevent database socket exhaustion and reduce overhead on PostgreSQL backend instances.

Deploying Kestra with Docker Compose and Traefik

We will orchestrate three main services using Docker Compose: Traefik (for automatic SSL certificates via Let’s Encrypt), PostgreSQL (state storage), and Kestra (the orchestration server).

First, create a network configuration file for Traefik to handle incoming requests securely. Create traefik/traefik.yml with the following explicit runtime settings:

api:
  dashboard: false

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

certificatesResolvers:
  letsencrypt:
    acme:
      email: your-email@example.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

providers:
  docker:
    exposedByDefault: false

Now, create an empty state storage file for Let’s Encrypt SSL certificates and adjust access permissions:

touch ~/kestra-stack/traefik/acme.json
chmod 600 ~/kestra-stack/traefik/acme.json

Create the full unified docker-compose.yml file in ~/kestra-stack/docker-compose.yml:

version: "3.8"

networks:
  kestra-network:
    driver: bridge

services:
  traefik:
    image: traefik:v2.10
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - kestra-network
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik/traefik.yml:/traefik.yml:ro
      - ./traefik/acme.json:/letsencrypt/acme.json

  postgres:
    image: postgres:15-alpine
    container_name: kestra-postgres
    restart: unless-stopped
    networks:
      - kestra-network
    environment:
      POSTGRES_DB: kestra
      POSTGRES_USER: kestra_user
      POSTGRES_PASSWORD: SuperSecretPassword123!
    volumes:
      - ./postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U kestra_user -d kestra"]
      interval: 5s
      timeout: 5s
      retries: 5

  kestra:
    image: kestra/kestra:latest
    container_name: kestra-engine
    restart: unless-stopped
    networks:
      - kestra-network
    command: server standalone
    depends_on:
      postgres:
        condition: service_healthy
    volumes:
      - ./config/application.yml:/app/configuration.yml:ro
      - /var/run/docker.sock:/var/run/docker.sock
      - /tmp/kestra-workdir:/tmp/kestra-workdir
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.kestra.rule=Host(`kestra.yourdomain.com`)"
      - "traefik.http.routers.kestra.entrypoints=websecure"
      - "traefik.http.routers.kestra.tls.certresolver=letsencrypt"
      - "traefik.http.services.kestra.loadbalancer.server.port=8080"

Replace kestra.yourdomain.com with your real domain name configured in Namecheap pointing directly to your VPS IP address. Also replace your-email@example.com in traefik/traefik.yml with your actual email address.

Start the complete application stack using Docker Compose:

docker compose up -d

Check container status logs to ensure everything compiles and boots properly:

docker compose logs -f kestra

Once initialized, navigate to https://kestra.yourdomain.com in your browser. You will be prompted to supply the admin username (admin@example.com) and password (StrongAdminPassword456!) specified in your application.yml file.

Writing Your First Kestra Workflow

With Kestra deployed, you can build automation flows directly inside the Web UI editor or via YAML definitions uploaded over the REST API.

Here is a multi-step workflow example that runs local scripts, processes HTTP requests, and outputs execution records.

Create a workflow file named sample-pipeline.yml:

id: system_health_pipeline
namespace: dev.ops

tasks:
  - id: log_start
    type: io.kestra.plugin.core.log.Log
    message: Starting automated server metrics check...

  - id: fetch_external_ip
    type: io.kestra.plugin.core.http.Request
    uri: https://api.ipify.org?format=json

  - id: parse_ip_response
    type: io.kestra.plugin.core.log.Log
    message: "Public IP response was: {{ outputs.fetch_external_ip.body }}"

  - id: run_shell_diagnostics
    type: io.kestra.plugin.scripts.shell.Commands
    runner: PROCESS
    commands:
      - echo "Current Disk Usage:"
      - df -h
      - echo "Current Memory Usage:"
      - free -m

triggers:
  - id: daily_schedule
    type: io.kestra.plugin.core.trigger.Schedule
    cron: "0 8 * * *"

To create this workflow via Kestra’s API directly from your terminal, execute the following curl command:

curl -X POST "https://kestra.yourdomain.com/api/v1/workflows" \
  -u "admin@example.com:StrongAdminPassword456!" \
  -H "Content-Type: application/x-yaml" \
  --data-binary @sample-pipeline.yml

You can now open the Web UI dashboard under the Flows tab, locate system_health_pipeline, and click Execute to trigger the flow manually. The platform presents detailed real-time execution logs, task topologies, and outputs for every step.

Getting Started

To launch your own production instance, acquire infrastructure resources through these providers:

Outsource Your Automation

Don’t have time? I build production n8n workflows, WhatsApp bots, and fully automated YouTube Shorts pipelines. Hire me on Fiverr, mention SYS3-HUGO for priority. Or DM at chasebot.online.

Want to automate this yourself?

Start with n8n Cloud (free tier available) or self-host on a Hetzner VPS for full control.

Want this engine running on your own VPS?

This blog publishes itself — daily, unattended, on free API tiers. The full engine, Hugo theme, and setup guide are available as System 3.

Get System 3
system online