Independent testing Updated August 2026 387 self-hosting guides 5 VPS providers tested

guide

How to Self-Host Windmill on a VPS (Complete Guide)

Deploy Windmill, the open-source Retool and Temporal alternative, on a VPS using the official compose stack. Real worker sizing, Caddy TLS, backups and scaling.

Windmill turns scripts into webhooks, workflows, cron jobs and internal UIs - an open-source alternative to Retool and Temporal. Self-hosting it is straightforward, with one catch worth knowing before you start: it does not use Redis, despite what a number of guides on the web will tell you.

Why Self-Host Windmill?

The cloud plan bills per seat and per execution. Self-hosting removes both meters, which matters for an automation platform, because the jobs you most want to run on a schedule are exactly the ones that would accumulate execution charges. It also keeps your scripts, credentials and job logs on your own machine, and those scripts routinely hold API keys for everything else you run.

The community edition is AGPL-3.0. The enterprise build sits behind a licence key and a different image tag; you do not need it to run the platform.

Choosing the Right VPS

Windmill sizes by worker, not by application. The rule of thumb is one worker per vCPU with 1-2 GB of RAM each, on top of Postgres and the server process.

ProviderPlanvCPU / RAM / DiskPriceNotesLink
ContaboCloud VPS 44 / 8 GB / 100 GB SSD5.50 EUR/mo netFour cores means four workers of parallelismContabo
Hetzner CloudCX232 / 4 GB / 40 GB NVMe5.49 EUR/mo netFits the default two-worker stackHetzner
DigitalOceanBasic 4 GB2 / 4 GB / 80 GB SSD24 USD/moSame specs as CX23 at four times the priceDigitalOcean
VultrRegular 4 GB2 / 4 GB / 80 GB SSD20 USD/moWidest region choiceVultr
LinodeLinode 4 GB2 / 4 GB / 80 GB SSD24 USD/moPredictable performanceLinode

Because throughput scales with worker count, core count matters more here than clock speed. For a detailed comparison, visit our full VPS comparison page.

Prerequisites

Step 1: Prepare Your VPS

Connect and update:

ssh root@your-vps-ip
apt update && apt upgrade -y

Install Docker and the Compose plugin:

apt install -y docker.io docker-compose-v2
systemctl enable --now docker
docker compose version

Open the ports Caddy will use:

ufw allow OpenSSH && ufw allow 80 && ufw allow 443
ufw enable

Step 2: Fetch the Official Compose Stack

Do not hand-write a compose file for Windmill. The project publishes one, and it wires the workers, indexer and language servers together correctly:

mkdir -p ~/windmill && cd ~/windmill
curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/windmill-labs/windmill/main/docker-compose.yml
curl -fsSL -o .env https://raw.githubusercontent.com/windmill-labs/windmill/main/.env
curl -fsSL -o Caddyfile https://raw.githubusercontent.com/windmill-labs/windmill/main/Caddyfile

The stack you just downloaded runs seven services:

There is no Redis in that list, and there should not be one in yours.

Step 3: Set Your Password and Domain

The shipped .env contains a deliberately weak default:

DATABASE_URL=postgres://postgres:changeme@db/windmill?sslmode=disable
WM_IMAGE=ghcr.io/windmill-labs/windmill:main

Change changeme to a generated secret before the first start, since the database is created on that first boot:

openssl rand -hex 24

Set BASE_URL to your domain in the same file. Caddy reads it to request a certificate.

Step 4: Start It

docker compose up -d
docker compose ps

The first start pulls several images and runs the database migrations, so give it a minute. Open your domain and create the first account - it becomes the instance superadmin.

Step 5: Add Workers as You Need Them

This is the part that makes Windmill different from most self-hosted apps. You do not scale by making one process bigger; you add worker containers. Copy the windmill_worker block in docker-compose.yml, give it a new service name, keep the same DATABASE_URL and MODE=worker, and apply:

docker compose up -d

Workers coordinate through Postgres, so no other configuration changes. Budget roughly a vCPU and 1-2 GB of RAM for each one.

Step 6: Back Up the Database

Everything that matters - scripts, flows, schedules, resources, job history - lives in Postgres. The dependency cache volume is derived data and rebuilds itself.

docker compose exec db pg_dump -U postgres windmill | gzip > windmill-$(date +%F).sql.gz

Copy it off the server. Note that Windmill stores credentials for your other systems, so treat that dump as a secret and encrypt it at rest.

Where People Get Caught

Copying a Redis-based compose file from a blog. Windmill has never needed one. The container will start and simply sit idle while you wonder why the queue is not using it.

Sizing on steady state. Dependency installation is the memory spike. A Python job importing pandas needs far more RAM the first time it runs than on any run after, so a stack that looks fine at idle can fail on a cold job.

Leaving changeme in place. It is the Postgres password in the published .env, and it is the first thing an opportunistic scan tries if you ever expose 5432.

Windmill rewards a machine with more cores rather than faster ones, which makes the cheap European multi-core plans unusually good value for it. Size for the number of jobs you want running at once, keep the Postgres dump somewhere encrypted, and it is an easy service to live with.

For more self-hosting projects and tips, browse the awesome-selfhosted list or the r/selfhosted community.

Frequently asked questions

Do I need Redis to self-host Windmill?

No, and this trips up a lot of people. Windmill uses PostgreSQL as both its database and its job queue, so the official compose file contains no Redis service and no REDIS_URL variable. Several third-party guides add one anyway. If you copied a compose file with a Redis container in it, you are running a service Windmill never connects to, and you can remove it safely.

How do I add more workers to a running Windmill instance?

Add another windmill_worker service to the compose file pointing at the same DATABASE_URL with MODE=worker, then run docker compose up -d. Workers coordinate through Postgres, so nothing else needs reconfiguring. The practical limit is your core count: each standard worker executes one job at a time and wants roughly a vCPU and 1-2 GB of RAM.

How do I put Windmill behind HTTPS?

The stack already ships Caddy, which serves plain HTTP on port 80 by default. To get automatic TLS, set BASE_URL to your domain and edit the Caddyfile and the caddy service to expose 443, as the comments in the shipped .env describe. Point DNS at the server before you start it so the certificate can be issued on first boot.