Why self-host Windmill on a VPS
Unlike purely no-code tools, Windmill targets teams that want to write real code but without reinventing orchestration, secret management, schedules and UIs. You drop in a Python or TypeScript function, and Windmill automatically derives an input form from it, exposes it as an API and adds it to the catalog. Self-hosting on a VPS is particularly valuable here: your scripts often touch internal databases, sensitive secrets and private networks. By keeping Windmill on your infrastructure, these scripts run as close as possible to your resources, without exposing your credentials to a third party. The engine is written in Rust, so it's fast and lightweight, and the project is designed for self-hosting from the start.
Concrete benefits of self-hosting
- Native Python/TypeScript/Go/Bash code versioned in Git, with no logic locked inside a proprietary UI.
- Management of encrypted secrets and variables, accessible only from your server.
- Automatic generation of UIs and APIs from your functions, with no front end to write.
- Scalable workers: add worker containers to absorb more jobs in parallel.
- Execution as close as possible to your internal databases, with minimal latency and a respected private network.
- No execution limits imposed by a SaaS vendor.
Technical requirements
Windmill relies on PostgreSQL and one or more workers. For a lightweight production instance, plan for 2 vCPU and 4 GB of RAM; the Rust engine is frugal, but each worker runs user code and can occasionally consume memory depending on your scripts. Allow 4 vCPU / 8 GB if you plan several workers and heavy Python jobs (data, pandas). Disk: 20 to 40 GB depending on the dependency cache. You need Docker and Docker Compose, a domain (windmill.yourdomain.com), and ports 80/443 open. PostgreSQL is included in the official compose.
Deploy Windmill with Docker Compose
Initialize the server
On the VPS, install Docker (curl -fsSL https://get.docker.com | sh), create /opt/windmill and move into it. Check the available memory with free -h before continuing.
Download the compose and the .env
Fetch the reference file: curl -O https://raw.githubusercontent.com/windmill-labs/windmill/main/docker-compose.yml and the associated .env. The compose defines the server, the workers and PostgreSQL.
Configure the database and base URL
In the .env, set DATABASE_URL with a strong password and BASE_URL=https://windmill.yourdomain.com. Adjust NUM_WORKERS according to your vCPU (start at 2). Leave the native worker for lightweight jobs.
Start the stack
Run docker compose up -d. Windmill automatically applies its PostgreSQL migrations on first startup. Monitor with docker compose logs -f windmill_server until the listening message appears.
Reverse proxy and HTTPS
The compose often includes a Caddy service. Otherwise, add your own Caddy: windmill.yourdomain.com { reverse_proxy windmill_server:8000 }. Caddy handles the Let's Encrypt certificate with no additional configuration.
First login and script test
Log in with the default credentials ([email protected] / changeme), change the password immediately, create a workspace, then write a Python script def main(name: str): return f'Hello {name}' to verify the automatic UI generation and execution on a worker.
For heavy Python scripts, create workers dedicated to a tag (worker_tags=gpu or heavy) and route resource-hungry jobs to these workers via the script's tag field. This prevents a pandas job from occupying all the workers and blocking fast automations. Windmill also caches pip/npm dependencies in a volume: mount it on a fast disk to drastically speed up the first startups of jobs.