Every git-push PaaS hits the same gap about six months after launch. Deploying a web service from a repo is solved — but then a tenant asks for the two things every real app needs next: something that runs on a schedule, and something that runs in the background. Cron and workers. The platform team now faces a build-vs-route decision, and there is a strong candidate for the "route" side: Windmill, the open-source workflow engine that turns scripts into webhooks, scheduled jobs, and internal UIs, and claims roughly 13x Airflow's throughput.
Here is the verdict up front: for a self-hosted PaaS, running Windmill as a deployable tenant workload beats building a first-party cron/background-job primitive — until three specific conditions flip the answer. The table below is the whole argument; the rest of this post is the evidence.
| Dimension | Route tenants to Windmill | Build first-party cron + workers |
|---|---|---|
| Time to ship | Days (Helm chart onto the fleet) | Weeks–months (API, scheduler, UI, retry semantics) |
| Tenant UX | Full workflow UI, retries, audit log on day one | Whatever you build; cron-only at first |
| Ops burden | One more stateful app (needs Postgres) + worker sizing | Native to the control plane, no extra datastore |
| Isolation model | Shared Windmill instance per tenant or per fleet | Per-app, inherits your existing tenant boundary |
| License | AGPLv3 community / Enterprise from ~$120/mo | Yours |
| Scale ceiling | Worker groups autoscale; ~26M jobs/worker claimed | You own the ceiling — and the pager for it |
The short version: Windmill buys you years of workflow-engineering R&D for the price of operating one stateful app. You should only build your own once tenant volume, isolation requirements, or license posture force you to — and this post names exactly where those lines are.
What Windmill actually is
Windmill (windmill-labs/windmill) is a Rust-built developer platform whose core idea is simple: a script in Python, TypeScript, Go, Bash, or SQL becomes a deployable unit — a webhook endpoint, a scheduled job, a step in a flow, or a generated UI — without a separate deploy pipeline per script. Flows compose steps with branching, retries, and error handling; schedules are cron expressions with timezone support; webhooks expose any script as an HTTP endpoint. Around that core sit worker groups (pools of executors that can be scaled and tagged per workload), a Postgres-backed job queue and state store, auto-generated input forms from parameter schemas, and an audit log of every run.
Deployment is Kubernetes-native: an official Helm chart installs the server, workers, and supporting services, with worker replica counts and autoscaling knobs exposed as values. Workers can run against an external managed Postgres (RDS, Cloud SQL, Neon) by setting DATABASE_URL, which matters for fleet operators who already run a database story and don't want another stateful singleton.
The headline number — ~13x Airflow's throughput — comes from Windmill's own published benchmarks, and it deserves the standard vendor-benchmark discount. The methodology pits a 40-lightweight-task flow and a 10-long-running-task flow against Airflow, Prefect, and Temporal.
Lightweight-task throughput is exactly where Airflow's scheduler-and-DB architecture is weakest and a compiled Rust executor with a lean queue is strongest, so the 13x figure measures the gap at its widest point, not at a typical mixed ETL workload. The honest reading: Windmill is genuinely architected for lower per-job overhead than Airflow (no scheduler heartbeat per DAG, no per-task pod churn by default), but treat 13x as a ceiling observed under favorable conditions, not a planning factor.
Even at a fraction of that multiple, it is fast enough that engine throughput won't be your bottleneck — worker sizing and Postgres headroom will.
Pricing is similarly two-tiered. The Community Edition is free and self-hostable under AGPLv3 (plus some proprietary bits in the distributed images under Windmill Labs terms — read the LICENSE file, not just the headline). Paid tiers start around $10 per developer seat per month on cloud, with self-hosted Enterprise from roughly $120 per month plus seats and worker-based compute units. For a PaaS operator, the relevant line is the license, not the seat price — more on that below.
What it costs to run on your own fleet
Routing tenants to Windmill is not free; it is one stateful app you now operate. Concretely, a minimal production install needs:
- The Windmill server (frontend + API + scheduler logic), typically 2 replicas for availability.
- Worker groups, sized per workload — lightweight script workers are cheap; workers executing heavy Python/ML jobs need real CPU and memory requests. The Helm chart exposes per-group resources, node selectors, and autoscaling up to a max replica count.
- Postgres, the non-negotiable dependency. Every job, run log, and state transition goes through it. The chart's bundled Postgres is demo-grade; production means pointing
DATABASE_URLat a Postgres you already back up and monitor. If your fleet doesn't have a managed-Postgres story yet, Windmill forces you to build one — which is arguably a benefit disguised as a cost. - Supporting services (language-server sidecars, object storage for large results) depending on which features tenants use.
On a Hetzner-class fleet this is a small number of pods plus database headroom — comfortably under €20/month in raw compute for a lightly used instance, dominated by whatever Postgres backing you choose. The real cost is operational surface: Windmill upgrades, worker-queue monitoring, and Postgres backup/restore drills now belong to your platform team.
Compare that against the alternative honestly: building cron + workers natively means designing retry semantics, run history, a tenant-facing trigger UI, and schedule management yourself — weeks of engineering plus permanent maintenance. One stateful app's ops burden is the cheaper side of that trade for any team under roughly a dozen engineers.
The decision: route or build?
This is the core deliverable — six dimensions, both sides totaled:
- Time to ship. Windmill via Helm: days, including TLS ingress and SSO wiring. First-party: a cron API, a worker execution model, retry/dead-letter semantics, run history storage, and at least a minimal UI — a quarter of engineering, minimum, before it matches Windmill's day-one surface.
- Tenant UX. Windmill ships a flow editor, auto-generated forms, per-run logs, and audit trails. A first-party v1 will be YAML-in-repo plus
kubectl-visible CronJobs. Tenants who have used any modern workflow tool will feel the gap immediately. - Ops burden. Windmill adds a stateful dependency (Postgres) and upgrade cadence you don't control. First-party adds code you own entirely — no license review, no upstream changelog surprises, fits your existing backup story.
- Isolation. This is the sharpest edge. A shared Windmill instance means tenant scripts execute on shared workers — acceptable for trusted tenants, questionable for mutually untrusted ones. Per-tenant Windmill instances fix isolation but multiply the Postgres and ops cost per tenant. A first-party primitive inherits your platform's existing tenant boundary for free.
- License. AGPLv3's network-copyleft clause triggers on modified versions offered over a network. Running unmodified Windmill Community Edition as a tenant-facing service is the common, widely practiced pattern — but "common" is not legal advice, and the Enterprise tier exists precisely for teams that want commercial terms. If your company's posture forbids AGPL anywhere near the serving path, this row alone decides the table.
- Scale ceiling. Windmill's worker-group model scales horizontally and its maintainers claim very high per-worker job counts; your ceiling is Postgres write throughput and worker capacity planning. First-party scales exactly as well as you design it — which is to say, you'll rediscover Windmill's hard-won lessons about queue backpressure at 2 a.m.
Sensitivity check: for a fleet with tens of tenants and trusted or semi-trusted workloads, rows 1–2 dominate and routing wins. Past hundreds of mutually untrusted tenants, rows 4–5 start dominating and the per-tenant-instance math or the license posture pushes toward building. The crossover isn't a job count — it's a trust-and-lawyers count.
Where each side wins, honestly
Route to Windmill when: you need cron + webhooks + retries this quarter, your tenants are teams or individuals who benefit from a workflow UI, and your lawyers clear AGPLv3 for an unmodified deployment (or the ~$120/mo Enterprise floor fits the budget). Gotchas to respect: size Postgres before you need to, pin worker-group resources per tenant tier so one tenant's fan-out can't starve others, and keep Windmill's version pinned with a tested upgrade path — upstream ships fast.
Build first-party when: tenants are mutually untrusted and per-tenant Windmill instances cost more than a native CronJob wrapper; your license posture bans AGPL; or your background-job needs are genuinely just "cron plus a queue" with no workflow UI demand — in which case Kubernetes CronJobs plus a thin API over them is a weekend, not a quarter, and Windmill would be overkill.
The pragmatic middle path most fleets actually land on: ship Windmill as the default jobs story now, expose plain CronJob-backed schedules as the lightweight primitive later, and let tenant demand sort workloads into the right tier. Nothing about running Windmill first precludes building native cron second — but the reverse order ships value a year later.
The takeaway for a git-push PaaS
Background jobs are where minimal PaaS platforms go to become real platforms or stall out. Windmill compresses that transition from quarters to days at the cost of one stateful app, one license review, and one shared-worker isolation decision. That is a good trade for nearly every self-hosted fleet at the stage where tenants are asking for cron — which is to say, every fleet with more than a handful of production apps.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.



