AIFlowPost

How to Self-Host Windmill (2026 Docker Guide)

Updated 2026-10-04 · 965 words

How to Self-Host Windmill (2026 Docker Guide) — AIFlowPost cover

This page may contain affiliate links. If you buy through them we may earn a commission at no extra cost to you.

Self-hosting Windmill gets you the full AGPLv3-licensed core, unlimited scripts, flows, and executions, running on your own server in about five minutes with Docker Compose. Unlike most self-hosted automation tools, the default stack ships with Postgres and automatic TLS already wired in, so the setup that usually takes the longest is mostly done for you. This guide covers the steps most tutorials skip: configuring BASE_URL correctly the first time and planning for worker scale before you actually need it.

What you need before you start

You need a server or VPS with Docker and Docker Compose v2 installed, at least 2 vCPU and 4GB of RAM for anything beyond local testing, and a domain name pointed at the server if you want it reachable from the public internet, which you'll need for any script triggered by an incoming webhook. PostgreSQL 14 or later is required for persistence, but you don't need to install it separately: the default compose file bundles it.

Step 1: Pull the official Docker Compose file

Create a working directory and download the official compose file directly from Windmill's repository:

mkdir -p /opt/windmill && cd /opt/windmill
curl -O https://raw.githubusercontent.com/windmill-labs/windmill/main/docker-compose.yml

This single file defines the Windmill server, a general-purpose worker, a dedicated worker for native scripts, the bundled Postgres database, and a Caddy reverse proxy for TLS.

Step 2: Set BASE_URL before you launch

This is the step that causes the most confusion later, and it's worth getting right before your first docker compose up. Windmill uses BASE_URL to build every webhook URL, approval link, and OAuth redirect it generates, so if it's wrong, those links will point somewhere that doesn't resolve.

If you're running locally or testing without a domain, the default localhost configuration works as-is. If you're deploying publicly, set BASE_URL in the caddy service block of the compose file to your actual domain, for example windmill.yourdomain.com. With a real hostname set here, Caddy automatically requests and renews a Let's Encrypt certificate and redirects plain HTTP to HTTPS, no separate certbot setup required.

Step 3: Launch the stack

Start everything with:

docker compose up -d

This brings up the Windmill server, both worker types, and Postgres. Give it a minute to initialize on first boot, Postgres needs to run its initial schema setup, then visit http://localhost for a local test, or your configured domain if you set one up publicly.

Step 4: Create your workspace and confirm the instance Base URL

The first account you create becomes the instance's superadmin; there's no default login to change afterward. Once you're in, open Instance Settings and confirm the Base URL field matches what you set in Step 2 exactly, including the https:// scheme if you configured a public domain. This is a separate setting from the Caddy environment variable, and mismatches between the two are the most common cause of broken webhook links reported by self-hosters.

Step 5: Harden it for production

Set every service's restart policy to unless-stopped so a crashed container recovers on its own. Pin the image tag to a specific release once you've confirmed the instance is stable, rather than tracking latest, so an upstream update doesn't change your running stack unexpectedly. Schedule a regular pg_dump of the Postgres volume to off-server storage: Windmill stores every version of every script in that database, so a lost database means lost script history, not just lost current state.

Step 6: Scale workers as script volume grows

Windmill's worker model is built to scale horizontally from the start. If you're running heavier or more frequent script executions, add workers with a one-line change:

docker compose up -d --scale windmill_worker=3

This runs three worker containers processing jobs in parallel, applied live without touching your data or restarting the whole stack. Because scripts are the unit of execution rather than a shared workflow engine, adding workers scales throughput close to linearly for most workloads.

Step 7: Update the stack safely later

When a new Windmill release comes out, pull the updated images and recreate the containers:

docker compose pull
docker compose up -d

Your scripts, flows, and execution history live in the Postgres volume, not inside the containers, so a routine update doesn't touch that data. The one exception worth planning around is a major version bump, which occasionally ships a database schema migration; run a fresh pg_dump immediately beforehand so you can roll back cleanly if anything about the upgrade doesn't go as expected.

If you're wondering whether the free core is enough, here's how it compares to the paid tier:

Community (AGPLv3) Enterprise
Scripts, flows, executions Unlimited Unlimited
SAML SSO No Yes
Audit logs No Yes
High-availability worker configs No Yes
Cost Free, self-hosted Paid

Common mistakes to avoid

Leaving BASE_URL mismatched between the Caddy service and the instance settings is the most common issue, it won't stop the instance from running, but every webhook URL and OAuth callback it generates will be wrong until you fix both places. Skipping the Postgres backup is the second: since every script version lives in that database, a lost volume with no backup means rebuilding your automation history from scratch, not just restoring uptime. And resizing a single worker instead of adding more workers under load is a common instinct that doesn't match how Windmill actually scales; horizontal worker count, not a bigger box, is the lever that matters here.

What's next

Once your instance is running, our Windmill review covers what held up over a week of real scripts-as-workflows, including where the auto-generated UI forms save real time. If you're still deciding between a code-first platform and a node-based canvas, Windmill vs n8n breaks down that trade-off in depth, and our best open source automation tools roundup covers where Windmill's AGPLv3 license sits next to the fully permissive alternatives.

Try Windmill

Frequently asked questions

Do I need the enterprise edition to self-host Windmill in production?

No. The AGPLv3 core has no feature gating on the workflow engine itself, unlimited scripts, flows, and executions run on the free self-hosted build. The enterprise edition adds SAML SSO, audit logs, and high-availability worker configurations, operational features for larger teams, not a requirement to run Windmill seriously.

What happens if I don't set BASE_URL correctly before launching?

The instance still runs, but every webhook URL, approval link, and OAuth redirect Windmill generates will point to the wrong address, usually localhost or whatever default was in the compose file. Scripts triggered by external webhooks will silently fail to receive callbacks until you fix BASE_URL in both the Caddy service and Instance Settings and restart.

How do I update Windmill without losing data?

Pull the latest image tags with `docker compose pull`, then `docker compose up -d` to recreate the containers; your data lives in the Postgres volume, not the containers themselves, so a routine update doesn't touch it. Still, run a `pg_dump` backup immediately before any update that changes a major version number, since that's when a breaking schema migration is most likely.

Can I run Windmill without Docker?

Officially, Docker Compose is the supported and documented path, and it's what gives you the bundled Postgres, Caddy, and worker orchestration with minimal manual configuration. Running the server and worker binaries directly outside containers is technically possible since the project is open source, but you'd be managing process supervision, networking, and TLS yourself with no official guide to follow.

↑ Back to top