别让脚本和后台任务各自为战:Windmill 自托管工作流的 CLI、同步与部署边界
当团队的自动化从几个 cron 脚本增长到几十个任务,真正难维护的通常不是某一段 Python,而是脚本、凭据、触发器、日志和部署环境彼此分离:有人改了服务器上的文件,却没有同步回仓库;有人知道任务失败,却找不到是哪一步;另一个人想复用逻辑,只能复制粘贴一份新脚本。Windmill 值得关注的地方,不是“又一个工作流画布”,而是把脚本、API、后台任务、工作流和可生成的界面放到同一套开发模型里。
Windmill 是 windmill-labs 维护的开源平台,主项目使用 AGPLv3,并提供自托管方式。它支持 Python、TypeScript、Go、Bash、SQL、GraphQL、PowerShell、Rust 等脚本语言;脚本可以成为 API 端点、后台任务或工作流步骤。对已经习惯 Git 和终端的开发者来说,最有价值的入口不是拖拽,而是 CLI 加本地同步:先在代码目录里写清楚,再推送到 Windmill 执行和观察。
先理解它解决的边界
Windmill 适合三种容易失控的场景。
第一种是周期任务。比如每天从数据库导出数据、调用一个 HTTP 接口,再把结果写入对象存储。用 cron 当然能启动脚本,但重试策略、执行历史和输入参数往往要自己补。Windmill 把脚本放进可观测的执行环境后,触发器和运行记录成为平台能力。
第二种是内部 API 和后台作业。一个已有的 Python 函数不必先改造成完整 Web 服务,平台可以围绕它生成可调用的接口和界面。这里的关键不是省掉所有工程工作,而是减少“写一个小工具却要搭一套服务骨架”的重复劳动。
第三种是多人维护的自动化代码。CLI 支持把工作区拉到本地、推回远端;代码审查可以围绕脚本和流程定义进行,而不是只在网页编辑器里点选。Windmill README 还列出 schedule、webhook、HTTP route、Kafka、WebSocket 和 email 等触发方向,说明它更接近自动化后端,而不只是定时器。
用 Docker Compose 建一个最小实例
官方仓库 README 给出的 Compose 路线需要下载三个文件,然后启动服务:
curl https://raw.githubusercontent.com/windmill-labs/windmill/main/docker-compose.yml -o docker-compose.yml curl https://raw.githubusercontent.com/windmill-labs/windmill/main/Caddyfile -o Caddyfile curl https://raw.githubusercontent.com/windmill-labs/windmill/main/.env -o .env docker compose up -d
启动后访问 http://localhost。首次部署时不要把示例账号当成生产凭据;官方 README 当前展示了默认登录信息,实际环境应在首次登录后立刻修改,并把 .env 当作敏感配置管理。需要接入托管 PostgreSQL 时,README 提供了设置 DATABASE_URL 并将数据库副本数调整为 0 的方向。这个细节很重要:自托管并不等于必须把数据库也放在同一台机器上。
如果团队已经使用 Kubernetes,官方 README 还给出 Helm 仓库和安装命令:
helm repo add windmill https://windmill-labs.github.io/windmill-helm-charts/ helm install windmill-chart windmill/windmill \ --namespace=windmill --create-namespace
Compose 适合先验证脚本和流程,Helm 则适合把 worker、数据库连接和升级策略纳入现有集群运维。不要因为两种方式都能启动就认为它们具有相同的可靠性边界:生产部署仍要单独设计备份、域名、身份认证、网络访问和 worker 资源限制。
CLI 的核心是“工作区即代码”
当前 npm registry 的 windmill-cli 包提供 wmill 命令,CLI README 给出的安装方式是:
npm install -g windmill-cli wmill upgrade
接着把远端工作区加入本地配置:
wmill workspace add wmill workspace switch
完成连接后,可以把远端资源拉到本地:
wmill sync pull
在本地修改脚本或流程定义,再推回平台:
wmill sync push
官方 CLI 文档建议使用 --yaml,因为 YAML 将成为默认编码格式。实践中可以把同步目录纳入 Git:每次变更先在分支中审查,再执行 wmill sync push。这样做并不能自动解决两个方向同时修改的冲突,但至少让“谁改了什么”有了版本记录。团队还应约定一个规则:平台网页上的临时修改必须尽快拉回仓库,否则下一次推送可能覆盖未提交的变更。
从命令行运行脚本,而不是猜参数
CLI README 明确支持用资源路径运行脚本或流程,并通过 --data 传入 JSON:
wmill flow/script run u/username/path/to/script --data @input.json
也可以从标准输入传递数据:
cat input.json | wmill script run u/username/path/to/script --data @-
这里有一个容易被忽略的边界:命令行只负责选择资源和传递输入,输入字段仍必须与脚本或流程定义一致。不要看到某个脚本存在,就自行推断它接受哪些参数;应从工作区定义或平台的输入声明中确认字段名和类型。CLI README 还说明,流程步骤和日志会在执行时自动流式输出,这使它适合接入本地调试、发布脚本或 CI,而不是只能在浏览器里观察结果。
例如,部署一个“每日拉取数据”的流程时,可以将认证信息作为平台凭据或环境配置注入,把日期、租户 ID 等非敏感参数作为输入。代码只读取参数,不把令牌写入 Git。失败时先查看步骤级日志,再判断是上游接口、输入校验、权限还是 worker 资源问题。把所有错误都归因于“工作流平台不稳定”,通常会掩盖真正的边界。
什么时候不要用 Windmill
它不是所有自动化的替代品。一次性的本地脚本、只需一条 systemd timer 的任务,没有必要为了可视化而引入完整平台。对极低延迟、强事务一致性的核心链路,也应先确认任务调度、队列和数据库语义是否满足要求,而不是只看“能运行”。
许可证同样需要认真区分:主项目 README 声明的是 AGPLv3,npm 上的 windmill-cli 包元数据则标记为 Apache 2.0。两者不能笼统地写成“Windmill 使用 Apache 许可证”。如果团队要把平台改造后作为网络服务提供,必须让法务根据实际部署和分发方式评估 AGPL 义务。
另一个现实问题是状态同步。wmill sync pull 和 wmill sync push 很方便,却不会替团队决定资源命名、环境隔离和发布审批。建议至少拆分开发、测试、生产工作区,给同步操作配合 Git 分支,并在生产推送前保留可回滚版本。对数据库、外部 API 和凭据的权限,也不要因为任务运行在“内部平台”里就放宽到管理员级别。
一个稳妥的落地顺序
比较实用的试用顺序是:先用 Compose 启动单机实例;再写一个没有敏感凭据的简单脚本,确认输入、日志和失败重试体验;接着安装 CLI,完成 workspace、pull、修改、push 的完整往返;最后才接入真实数据库和 webhook。上线前还要明确谁能发布、谁能查看日志、谁能读取凭据,以及失败通知发给哪个值班渠道。等资源模型、权限和备份方式明确后,再考虑 Helm 或外部 PostgreSQL。试用期间最好故意制造一次输入错误和一次外部接口超时,观察平台记录的状态、日志和重跑行为;这比只验证一条成功路径更能暴露运维成本。
Windmill 的运行结构也值得单独看一眼。前端负责资源和执行入口,后端保存定义并协调执行,worker 承担实际脚本运行;官方 README 还提到 PostgreSQL、Rust 后端、Svelte 5 前端,以及 nsjail/PID namespace 隔离。对简单任务来说这些名词不是选型理由,但当脚本会执行外部命令、处理用户输入或并行运行时,隔离方式和 worker 数量就会直接影响风险与吞吐。部署前应先确认哪些步骤能访问网络、哪些凭据能被读取,以及任务超时后是否会留下重复副作用。
另一个值得提前约束的点是幂等性。工作流平台可以帮你重试失败步骤,却不能保证第三方接口不会收到两次请求。写入数据库、发送邮件或创建云资源前,最好使用业务唯一键、幂等 token 或状态表;把“重试”理解成免费再执行一次,是自动化系统最容易出现的生产事故来源。