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.
| Provider | Plan | vCPU / RAM / Disk | Price | Notes | Link |
|---|---|---|---|---|---|
| Contabo | Cloud VPS 4 | 4 / 8 GB / 100 GB SSD | 5.50 EUR/mo net | Four cores means four workers of parallelism | Contabo |
| Hetzner Cloud | CX23 | 2 / 4 GB / 40 GB NVMe | 5.49 EUR/mo net | Fits the default two-worker stack | Hetzner |
| DigitalOcean | Basic 4 GB | 2 / 4 GB / 80 GB SSD | 24 USD/mo | Same specs as CX23 at four times the price | DigitalOcean |
| Vultr | Regular 4 GB | 2 / 4 GB / 80 GB SSD | 20 USD/mo | Widest region choice | Vultr |
| Linode | Linode 4 GB | 2 / 4 GB / 80 GB SSD | 24 USD/mo | Predictable performance | Linode |
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
- A VPS running Ubuntu 24.04 LTS with at least 2 vCPU and 4 GB RAM
- Docker Engine and Compose v2
- A domain pointed at the server if you want TLS
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:
db-postgres:16, the database and the job queuewindmill_server- the API and web UI on port 8000windmill_worker- executes jobswindmill_worker_native- lightweight jobs at about 0.1 CPU and 128 MBwindmill_indexer- full-text search over job historywindmill_extra- language servers that power the in-browser editorcaddy- reverse proxy on port 80
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.