Windmill Labs
Windmill

Infrastructure automation on one open source orchestrator

Wrap the Terraform or OpenTofu modules, Ansible playbooks and scripts you already have in flows with approvals, retries and audit logs. Provision on vSphere, Kubernetes or cloud, then run the Day 2 jobs from the same place.

✓OpenTofu, Terraform, Ansible, PowerShell, Python and Bash in one flow
✓Approval steps and self-service forms generated from the inputs
✓ServiceNow requests start flows, flows close the tickets
✓Workers inside the network segment that owns the infrastructure

Infrastructure automation ends up in five places

Terraform runs from a CI pipeline, playbooks from AWX, the catalog lives in vRA or ServiceNow, patching is a cron job on a jump host, and the cleanup scripts sit in someone's home directory. Each tool has its own credentials, its own logs and its own idea of who is allowed to run what. Answering "who changed this VM, and who approved it" means opening four of them.

Windmill does not replace Terraform, OpenTofu or Ansible: it runs them. A flow takes typed inputs, calls each tool as a step, pauses for approval where policy says so, retries what is safe to retry, and keeps the inputs, logs and approver of every run. The same platform runs the schedules, the event-driven remediation and the self-service forms, so there is one place to look.

OpenTofu and Terraform runs with shared inputs and secrets

Windmill has no proprietary Terraform step: a script calls the tofu or terraform CLI on a worker where the binary is installed, through a custom image or preinstalled binaries. Credentials come from resources as typed arguments and reach the CLI as environment variables. State stays in the backend you choose, and the plan and apply output stream to the run log.

See the full script
f/infra/tofu_vm.py
def main(
    action: Literal["apply", "destroy"],
    vm_name: str,
    vsphere: c_vsphere,      # resource, picked in the form
    tf_state: postgresql,    # pg backend, one workspace per VM
    cpus: int = 2,
    memory_mb: int = 4096,
    dry_run: bool = False,
):
    env = {
        "PG_CONN_STR": _pg_conn_str(tf_state),
        "TF_VAR_vsphere_password": vsphere["password"],
        ...
    }
    _run("tofu", ["init", "-input=false"], env)
    _run("tofu", ["workspace", "select", "-or-create", vm_name], env)
    _run("tofu", ["apply", "-auto-approve"], env)

Ansible playbooks as a native script language

A playbook is a script in Windmill. A short header declares its inputs, inventories, files and Galaxy collections; Windmill generates the form, installs the dependencies, writes SSH keys from secrets with the right mode, and passes the inputs as --extra-vars. Write result.json and the next step of the flow gets a structured result.

Ansible quickstart
f/infra/configure_baseline.playbook.yml
---
extra_vars:
  host_ip:   { type: string }
  hostname:  { type: string }
files:
  - variable: f/infra/ssh_private_key
    target: ./ssh_key
    mode: '0600'
dependencies:
  galaxy:
    collections:
      - name: community.general
---
- hosts: localhost
  tasks:
    - add_host:
        name: "{{ hostname }}"
        ansible_host: "{{ host_ip }}"
        groups: new_vm
- hosts: new_vm
  become: true
  tasks: ...

Approvals and self-service requests

A flow's input schema becomes a form with enums, patterns and defaults, which is the catalog item. An approval step suspends the run until someone approves from the run page, a resume link, Slack or Teams, and a condition decides when it applies: prod only, large sizes only. For a richer front end, build an app on top of the same flow. Approval forms and approver restrictions are Cloud and Enterprise features.

Learn more about generated forms
Windmill flow run detail with per-step status, logs and results

The ticket lifecycle, closed by the flow

Keep the catalog in ServiceNow if that is where requesters live. A business rule calls the flow's webhook or HTTP route, a preprocessor maps the request to typed inputs, and the last step writes the result back on the RITM. When a step fails, the flow's failure handler rolls back and puts the error on the ticket.

See the provisioning flow
provision_vm__flow/preprocessor.ts
export async function preprocessor(event: Event) {
  const body = event.body ?? {};
  const v = body.variables ?? body;
  return {
    vm_name: String(v.vm_name).toLowerCase(),
    size: v.size ?? "small",
    os: v.os ?? "ubuntu-24.04",
    environment: v.environment ?? "dev",
    ticket: body.number,   // RITM0010001
  };
}

Run the job next to the infrastructure

Tag a script vsphere-dc1 and only the workers in that data center pick it up. For segments that should not open inbound ports, agent workers connect out to Windmill over HTTP and never touch its database. When the only way in is a jump host, SSH execution reroutes a Bash script over SSH. Agent workers and SSH execution are Enterprise features; worker tags are in every edition.

Learn more about worker groups
vsphere-dc1
  • · Terraform / OpenTofu + vSphere
  • · pyvmomi
  • · Ansible to DC1 VMs
k8s-prod
  • · kubectl / Helm
  • · Cluster maintenance
  • · Namespace audits
ot-plant
  • · Agent worker
  • · Outbound HTTP only
  • · No DB access
windows
  • · PowerShell / PowerCLI
  • · Native Windows worker
  • · Active Directory

Start from working flows

The windmill-vmware-vra-replacement repository holds the flows on this page as files you push with wmill sync push: VM provisioning and decommissioning on vSphere, rolling patching, the snapshot sweeper and the certificate check. Each flow has a dry_run input, so you can walk it before pointing it at vCenter.

terminal
git clone https://github.com/windmill-labs/windmill-vmware-vra-replacement
cd windmill-vmware-vra-replacement
wmill workspace add infra <workspace_id> https://windmill.example.com/
wmill sync push
wmill flow preview f/infra/rolling_patch__flow \
  -d '{"hosts":["web-01","web-02","web-03"],"require_approval":false,"dry_run":true}'

Moving off vRA, vRO, Rundeck or cron

Most infrastructure teams come to Windmill to replace a workflow engine, not their hypervisor. vRO workflows become flows, JavaScript actions become TypeScript scripts with few changes, configuration elements become resources, and packages become a Git repository. The guide covers what moves, what stays, and how to size the migration.

Read the vRA replacement guide

Frequently asked questions

Put your infrastructure automation in one place

Self-host Windmill, push the example flows, and run the first provisioning request in dry run.