tva
← 洞察

2026 年自托管 Windmill:工作进程、PostgreSQL、隔离与安全升级

这篇面向生产环境的最新后续文章,将厂商指南转化为运维控制措施、迁移决策和可验证的发布标准。

2026 年发生了哪些变化

我们重新审视这一主题,是因为系统的运行边界已经发生变化。长期有效的原则依然值得保留,但在当前版本下,一些过去的捷径已经不再完整,甚至可能带来风险。本文以截至 2026 年 7 月 14 日可获得的厂商文档为起点,区分客观事实与本部署环境中的决策,并将每一次配置调整视为受控的生产变更。

当前一手资料

如何使用本次更新

首先阅读一手资料,记录实际部署的版本,并在修改设置前定义可观测的目标结果。应在具有代表性的环境中测试最小且可回退的改动。命令执行成功并不等于验收通过;服务健康、数据完整性、延迟、安全边界和回滚时间才是标准。旧文中仍然有效的诊断顺序会作为运维基础保留在下文,但所有与版本有关的示例都必须对照当前文档复核。

发布前,应保存配置差异、至少完成过一次恢复验证的备份或快照,以及撤销本次变更所需的命令。安排一人观察首个生产窗口,另一人在关键指标朝错误方向变化时负责批准升级处理。告警应说明用户可感知的风险,而不只是指出发出告警的组件。在回读核验窗口内,将错误率、队列深度、资源饱和度、处理延迟和数据完整性与既定基线比较。既要检查平均值,也要观察尾部指标表现,因为稳定的平均值可能掩盖只影响少量但重要任务的故障。只有在延迟任务、重试、定时任务和下游导出全部完成后,才能结束本次变更的验证。恢复过程中采取的每一项人工操作都要记录;如果运维手册中没有这一步,就说明手册仍不完整。这种发布规范形成的证据可在下次升级时复用,避免团队重新摸索相同的依赖关系。

运维基础

工作流自动化平台对现代开发团队至关重要,但随着使用量增长,Windmill Cloud 等云解决方案可能变得昂贵。我们将向您展示如何在 Ubuntu 上使用 Docker Compose 和 Traefik 集成部署自己的 Windmill 实例,同时克服可能导致安装失败的关键 PostgreSQL 身份验证问题。

您将构建的内容

完成本教程后,您将拥有:

  • 功能完整且支持 HTTPS 的 Windmill 实例
  • 通过 Traefik 自动获取 Let’s Encrypt SSL 证书
  • 正确配置身份验证、可用于生产环境的 PostgreSQL 数据库
  • 资源优化的工作进程配置
  • 与现有 Docker 基础设施的集成
  • 可用于生产环境的专业工作流自动化配置

月度成本:€5.99 起,不含增值税,含一个主 IPv4 地址(Hetzner CX23,4GB RAM,截至 2026 年 10 月)+ 域名成本 – 同一套基础设施可运行多个自动化工具

前提条件

  • 已安装 Docker 和 Docker Compose 的 Ubuntu 24.04 LTS 服务器
  • 现有的 Traefik 反向代理设置(参见我们的 n8n 部署指南了解 Traefik 配置)
  • 指向您服务器 IP 的域名
  • 建议至少 4GB RAM 和 2 个 vCPU
  • SSH 访问权限和基本命令行知识

了解 Windmill

Windmill 是一个开源工作流引擎,提供:

  • 可视化工作流编辑器,支持 TypeScript/Python/Go
  • 任务调度和执行管理
  • API 集成能力
  • 团队协作功能
  • 可自托管,免费的 Community Edition 支持无限次执行

与 n8n 基于节点的方式不同,Windmill 专注于代码优先的工作流,配备强大的开发环境。

步骤 1:服务器准备和目录结构

首先,准备服务器环境。我们将使用食物命名约定来避免冲突:

# Create Windmill directory (first instance: "pizza")
mkdir -p /opt/windmill-pizza
cd /opt/windmill-pizza

# Create required subdirectories
mkdir -p postgres-data windmill-data lsp-cache

# Verify directory structure
ls -la

命名约定: 为多个 Windmill 实例使用简单的食物名称:

  • 第一个实例:pizza
  • 额外实例:pasta、salad、soup、burger 等
  • 这可以避免冲突并简化管理

步骤 2:环境配置

使用适当的凭据创建安全的环境文件:

# Generate a secure hex password (no special characters!)
SECURE_PASSWORD=$(openssl rand -hex 16)

# Create environment file
cat > /opt/windmill-pizza/.env << EOF
# Windmill Image Version (pin a release; change it deliberately when you update)
WM_IMAGE=ghcr.io/windmill-labs/windmill:1.767.0

# Database Configuration - SECURE PASSWORDS
DATABASE_URL=postgresql://postgres:${SECURE_PASSWORD}@windmill-db:5432/windmill_pizza?sslmode=disable
POSTGRES_PASSWORD=${SECURE_PASSWORD}
POSTGRES_DB=windmill_pizza
POSTGRES_USER=postgres

# Windmill Configuration
BASE_URL=https://windmill.yourdomain.com
RUST_LOG=info

# Worker Configuration
WORKER_GROUP=default
KEEP_JOB_DIR=false

# Instance Identifier
INSTANCE_NAME=pizza
EOF

# The file holds the database password: make it readable by its owner only
chmod 600 /opt/windmill-pizza/.env

关键: 将 windmill.yourdomain.com 替换为您的实际域名!

步骤 3:Docker Compose 配置

创建主 Docker Compose 配置:

cat > /opt/windmill-pizza/docker-compose.yml << 'EOF'
services:
  # PostgreSQL Database for Windmill
  windmill-db:
    image: postgres:16
    container_name: windmill-pizza-db
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
    networks:
      - proxy
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Windmill Server
  windmill-server:
    image: ${WM_IMAGE}
    container_name: windmill-pizza-server
    restart: unless-stopped
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - BASE_URL=${BASE_URL}
      - RUST_LOG=${RUST_LOG}
      - MODE=server
    networks:
      - proxy
    depends_on:
      windmill-db:
        condition: service_healthy
    volumes:
      - ./windmill-data:/tmp/windmill
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.windmill-pizza.rule=Host(`windmill.yourdomain.com`)"
      - "traefik.http.routers.windmill-pizza.entrypoints=https"
      - "traefik.http.routers.windmill-pizza.tls.certresolver=letsencrypt"
      - "traefik.http.services.windmill-pizza.loadbalancer.server.port=8000"

  # Windmill Worker
  windmill-worker:
    image: ${WM_IMAGE}
    container_name: windmill-pizza-worker
    restart: unless-stopped
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - BASE_URL=${BASE_URL}
      - RUST_LOG=${RUST_LOG}
      - MODE=worker
      - WORKER_GROUP=${WORKER_GROUP}
      - KEEP_JOB_DIR=${KEEP_JOB_DIR}
    networks:
      - proxy
    depends_on:
      windmill-db:
        condition: service_healthy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./windmill-data:/tmp/windmill
      - worker_dependency_cache:/tmp/windmill/cache
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 2G

networks:
  proxy:
    external: true

volumes:
  worker_dependency_cache:
    driver: local
EOF

重要: 更新 Traefik 标签中的域名以匹配您的设置!

步骤 4:PostgreSQL 密码问题(关键问题)

这是大多数 Windmill 安装失败的地方,经过大量排查才找到根本原因:

问题:密码中的特殊字符

使用 openssl rand -base64 32 生成密码时,输出可能包含 +、/ 和 =。在 DATABASE_URL 中,这些字符必须进行百分号编码;未经编码时,它们可能导致 URL 解析出错,并表现为 PostgreSQL 身份验证失败。使用十六进制密码则完全无需这一步编码。

有问题的密码示例:

# Unless percent-encoded, the "/" in this password breaks DATABASE_URL:
PASSWORD="305t6m9KrChkvbyN/FLEYQ6pqAGlApn9rbJPH3D5y9g="

解决方案:纯十六进制密码

使用不含特殊字符的纯十六进制密码:

# This WORKS reliably:
PASSWORD=$(openssl rand -hex 16)
# Example result: 5781b14ec0ec1bc184653ffa5e379411

其他 PostgreSQL 配置问题

  • 用户配置:使用 postgres 超级用户,与 Windmill 的示例配置一致。自定义用户只有在被授予 Windmill 迁移所需的 windmill_admin 和 windmill_user 角色后才能正常使用(参见 Windmill 自托管文档)
  • 数据卷持久化:当现有数据卷包含不同凭据时,PostgreSQL 会忽略 POSTGRES_PASSWORD 环境变量
  • URL 格式:当数据库只能通过 Docker 内部网络访问时,在数据库 URL 中加入 ?sslmode=disable,与 Windmill 的示例配置一致;对于通过其他网络访问的数据库,请使用 require 等 SSL 模式

步骤 5:安装和启动

现在使用修正后的配置安装 Windmill:

cd /opt/windmill-pizza

# Verify configuration syntax
docker compose config --quiet

# Pull images
docker compose pull

# Start services
docker compose up -d

# Check status
docker compose ps

您应该看到如下输出:

NAME                    STATUS                    PORTS
windmill-pizza-db       Up (healthy)              5432/tcp
windmill-pizza-server   Up                        8000/tcp
windmill-pizza-worker   Up                        8000/tcp

步骤 6:常见问题排查

问题 1:PostgreSQL 身份验证失败

症状:

windmill-pizza-server | Error: password authentication failed for user "postgres"

解决方案:重置数据库会删除所有 Windmill 数据。仅在全新安装时或已有经过验证的备份后执行此操作。

# WARNING: this deletes all Windmill data (fresh install or verified backup only)
cd /opt/windmill-pizza

# Stop containers
docker compose down --volumes

# Remove old data
rm -rf /opt/windmill-pizza/postgres-data/*

# Regenerate hex password and write it to both variables
NEW_PASSWORD=$(openssl rand -hex 16)
sed -i "s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$NEW_PASSWORD/" .env
sed -i "s|^DATABASE_URL=postgresql://postgres:[^@]*@|DATABASE_URL=postgresql://postgres:$NEW_PASSWORD@|" .env

# Restart
docker compose up -d

问题 2:容器无法启动

症状:

  • 容器立即退出
  • 资源分配错误

解决方案:

# Check container logs
docker logs windmill-pizza-server
docker logs windmill-pizza-db

# Check system resources
docker stats
free -h

问题 3:SSL 证书问题

症状:

  • HTTPS 无法正常访问
  • 证书错误

解决方案:

# Check Traefik logs
docker logs traefik

# Verify DNS resolution
nslookup windmill.yourdomain.com

# Restart Traefik if needed
docker restart traefik

步骤 7:访问和初始设置

安装完成后:

  • 访问 Windmill: https://windmill.yourdomain.com
  • 默认凭据:

邮箱:[email protected]

  • 密码:changeme
  • 完成设置:

更改管理员密码

  • 配置基础 URL
  • 设置用户账户

步骤 8:资源优化

内存分配(8GB 服务器)

我们的配置高效分配资源:

  • Windmill 服务器:约 800MB
  • Windmill 工作进程:2GB(限制)
  • PostgreSQL:约 500MB
  • 系统预留:约 4.7GB

CPU 分配(4 vCPU 服务器)

  • 工作进程:1 vCPU(限制)
  • 其他服务:3 vCPU(共享)

扩展规则: 每个 vCPU 配 1 个工作进程,每个 1-2GB RAM

步骤 9:生产环境安全加固

创建备份脚本

cat > /opt/windmill-pizza/backup.sh << 'EOF'
#!/bin/bash
set -euo pipefail
umask 077

# docker compose needs the project directory, also when run from cron
cd /opt/windmill-pizza

DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE=/opt/backups/windmill_pizza_${DATE}.sql
mkdir -p /opt/backups

if ! docker compose exec -T windmill-db pg_dump -U postgres windmill_pizza > "$BACKUP_FILE" || [ ! -s "$BACKUP_FILE" ]; then
  rm -f "$BACKUP_FILE"
  echo "Backup FAILED" >&2
  exit 1
fi
echo "Backup created: $BACKUP_FILE"
EOF

chmod +x /opt/windmill-pizza/backup.sh

设置监控

cd /opt/windmill-pizza

# Monitor container health
docker compose ps

# Check resource usage
docker stats

# Monitor logs
docker compose logs -f windmill-server

配置自动更新

cat > /opt/windmill-pizza/update.sh << 'EOF'
#!/bin/bash
set -euo pipefail
cd /opt/windmill-pizza

# Back up first; stop here if the backup fails
./backup.sh

# Pull and start the images set in .env (raise WM_IMAGE there to update Windmill)
docker compose pull
docker compose up -d
EOF

chmod +x /opt/windmill-pizza/update.sh

步骤 10:高级配置

与现有 SMTP 集成

如果您已有邮件服务器(例如按照我们的 n8n 部署指南部署的服务器),可以将其集成:

# Add to docker-compose.yml environment for windmill-server:
- SMTP_HOST=mailserver
- SMTP_PORT=25
- [email protected]
# Only if your mail server requires authentication:
# - SMTP_USERNAME=...
# - SMTP_PASSWORD=...

多个 Windmill 实例

对于需要隔离环境的团队:

# Create second instance from the configuration files only (not the data)
mkdir -p /opt/windmill-pasta/{postgres-data,windmill-data,lsp-cache}
cp /opt/windmill-pizza/{.env,docker-compose.yml,backup.sh,update.sh} /opt/windmill-pasta/
chmod 600 /opt/windmill-pasta/.env
cd /opt/windmill-pasta

# Update names, paths and domain
sed -i 's/pizza/pasta/g' .env docker-compose.yml backup.sh update.sh
sed -i 's/windmill.yourdomain.com/pasta.yourdomain.com/g' .env docker-compose.yml

# Generate new password and write it to both variables
NEW_PASSWORD=$(openssl rand -hex 16)
sed -i "s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$NEW_PASSWORD/" .env
sed -i "s|^DATABASE_URL=postgresql://postgres:[^@]*@|DATABASE_URL=postgresql://postgres:$NEW_PASSWORD@|" .env

# Start the new instance
docker compose up -d

安全考量

网络隔离

  • PostgreSQL 只能由其 Docker 网络上的容器访问;如果该网络与反向代理及其他服务共享,请使用独立的内部网络以实现更严格的隔离
  • 不向外部暴露数据库端口
  • 在 Traefik 层终止 HTTPS 连接

资源限制

  • 工作进程容器有 CPU 和内存限制
  • 资源限制可减轻失控任务及资源耗尽尝试的影响
  • 可根据服务器容量配置

SSL 安全

  • 自动获取 Let’s Encrypt 证书
  • HTTP 到 HTTPS 重定向
  • 现代 TLS 配置

监控与维护

每周健康检查

cd /opt/windmill-pizza

# Container status
docker compose ps

# Resource usage
docker stats --no-stream

# Log analysis
docker compose logs | grep -i error

每月维护

# Back up and update containers (update.sh runs backup.sh first)
cd /opt/windmill-pizza
./update.sh

# Clean unused images, containers and networks
docker system prune -f

成本明细与对比

月度成本

自托管设置:

  • Hetzner CX23(4GB RAM):€5.99/月,不含增值税,含一个主 IPv4 地址(截至 2026 年 10 月)
  • 域名成本:约 €1/月
  • 总计:约 €7/月,不含增值税

Windmill Cloud 对比:

  • Cloud:每个开发者席位 $20/月,每位操作员 $10/月(截至 2026 年 10 月)
  • 节省:当两名或更多开发者需要席位时,每年可节省 $250+

性能优势

自托管优势:

  • 不限工作流执行次数
  • 无外部速率限制
  • 完全控制数据
  • 自定义集成
  • 灵活扩展资源

故障排查参考

快速诊断

cd /opt/windmill-pizza

# Container health
docker compose ps | grep -E "(healthy|Up)"

# Network connectivity
docker network inspect proxy

# Database connection
docker compose exec windmill-db psql -U postgres -d windmill_pizza -c "SELECT version();"

# Log analysis
docker compose logs --tail 50 windmill-server | grep -E "(ERROR|WARN)"

常见错误模式

  • “password authentication failed” → 使用十六进制密码;仅在备份后或全新安装时清除卷
  • “connection refused” → 检查网络配置
  • “certificate errors” → 验证 DNS 和 Traefik 设置
  • “out of memory” → 调整工作进程资源限制

扩展您的 Windmill 基础设施

水平扩展

适用于高负载环境:

# Add additional workers (under services: in docker-compose.yml)
windmill-worker-2:
  image: ${WM_IMAGE}
  container_name: windmill-pizza-worker-2
  restart: unless-stopped
  environment:
    - DATABASE_URL=${DATABASE_URL}
    - MODE=worker
    - WORKER_GROUP=heavy
  networks:
    - proxy
  depends_on:
    windmill-db:
      condition: service_healthy
  deploy:
    resources:
      limits:
        cpus: '2'
        memory: 4G

垂直扩展

升级服务器资源(Hetzner 价格,不含增值税,含一个主 IPv4 地址,截至 2026 年 10 月):

  • CX33(8GB RAM):€8.99/月,适用于重负载
  • CX43(16GB RAM):€16.49/月,适用于企业级使用

与现有基础设施的集成

与 n8n 协同

如果您已在运行 n8n(来自我们之前的教程):

  • Windmill 处理代码优先的工作流
  • n8n 处理可视化的简单自动化
  • 两者共享同一 Traefik 代理
  • 独立数据库防止冲突

共享服务

利用现有基础设施:

  • Traefik:为所有服务处理 SSL
  • 邮件服务器:共享 SMTP 用于通知
  • 监控:统一的日志和指标
  • 备份:集中备份策略

总结

自托管 Windmill 以云托管成本的一小部分提供企业级工作流自动化。成功的关键是理解 PostgreSQL 凭据的传递和持久化方式,并使用纯十六进制密码来避免可能导致安装失败的 URL 编码问题。

此设置的关键优势

  • 经济高效:与云解决方案相比每年节省数百的费用
  • 可用于生产环境:可靠处理企业级工作负载
  • 安全:HTTPS、不对外暴露数据库端口和资源限制
  • 可扩展:可根据需要轻松添加工作进程和资源
  • 私密:您的代码和数据保留在您的基础设施上,除非您的工作流将其发送至外部服务

此配置已在生产环境中经过测试,提供了业务关键工作流自动化所需的可靠性。故障排查步骤解决了部署过程中遇到的实际问题,特别是影响许多自托管安装的 PostgreSQL 身份验证问题。

对于复杂的工作流需求或企业部署,建议咨询专业人士以优化您的具体使用场景并确保最佳资源分配。

下一步

  • 探索适用于 Windmill 的 webhook 故障排查技术
  • 查看处理大型数据集的数据载荷优化
  • 考虑与现有 n8n 安装的集成

关于 tva

tva 提供数据库系统、云环境和全球供应链的综合基础设施管理。我们系统化的方法将严格的安全协议与性能优化相结合,同时战略咨询服务实现数字能力和实物资产的精准协调——在所有业务中保持最高标准的卓越运营和合规水平。

访问 tva.sg 了解更多关于我们服务和其他自动化教程的信息。

从配置调整到运行决策

实际问题并不是平台能否完成配置,而是团队能否明确责任归属、发现配置偏移、不依赖临场发挥完成恢复,并证明改动改善了预期结果。因此,我们为配置配套负责人、基线、回滚路径和回读核验窗口,将一次性修复转化为运维能力。生产变更记录应包含变更原因、审查过的证据、确切范围,以及有权停止或撤销变更的人员。发布后,将约定指标与基线比较,并把结果保存在变更记录中。若未出现预期改善,回滚也是有效结果,而非失败。这种规范比任何单个配置值都更重要。同一份记录也为下一位运维人员提供可信的起点,使后续优化成为有测量依据的决策,而不是新一轮猜测。

相关洞察

延伸阅读