在 Ubuntu 上自托管 Windmill:Docker 部署与 PostgreSQL 故障排查完整教程
工作流自动化平台对现代开发团队至关重要,但随着使用量增长,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)
echo "Generated password: $SECURE_PASSWORD"
# Create environment file
cat > /opt/windmill-pizza/.env << EOF
# Windmill Image Version
WM_IMAGE=ghcr.io/windmill-labs/windmill:main
# Database Configuration - SECURE PASSWORDS
DATABASE_URL=postgresql://postgres:${SECURE_PASSWORD}@windmill-pizza-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
chmod 600 /opt/windmill-pizza/.env
关键: 将 windmill.yourdomain.com 替换为您的实际域名!
步骤 3:Docker Compose 配置
创建主 Docker Compose 配置:
cat > /opt/windmill-pizza/docker-compose.yml << 'EOF'
version: "3.8"
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 身份验证失败。十六进制密码可以完全省去这一步编码。
有问题的密码示例:
# This WILL cause authentication failures:
PASSWORD="305t6m9KrC/kvbyNEF+EYQ6pqAGlApn9rbJPH3D5y9g="
解决方案:纯十六进制密码
使用不含特殊字符的纯十六进制密码:
# 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 数据库中的所有数据。仅在全新安装或已有经过验证的备份时使用。两条 sed 命令会将新密码写入 POSTGRES_PASSWORD 和 DATABASE_URL。
# Stop containers
docker compose down --volumes
# Remove old data
rm -rf postgres-data/*
# Regenerate hex password
NEW_PASSWORD=$(openssl rand -hex 16)
sed -i "s/POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$NEW_PASSWORD/" .env
sed -i "s#postgresql://postgres:[^@]*@#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
cd /opt/windmill-pizza
DATE=$(date +%Y%m%d_%H%M%S)
FILE=/opt/backups/windmill_pizza_${DATE}.sql
mkdir -p /opt/backups
if ! docker compose exec -T windmill-db pg_dump -U postgres windmill_pizza > "$FILE"; then
rm -f "$FILE"
echo "Backup failed" >&2
exit 1
fi
echo "Backup created: $FILE"
EOF
chmod +x /opt/windmill-pizza/backup.sh
设置监控
# 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
cd /opt/windmill-pizza
docker compose pull
docker compose up -d --force-recreate
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
- SMTP_USERNAME=
- SMTP_PASSWORD=
- [email protected]
多个 Windmill 实例
对于需要隔离环境的团队,第二个实例使用自己的空数据目录、数据库和密码:
# Create second instance with its own empty data directories
mkdir -p /opt/windmill-pasta/postgres-data /opt/windmill-pasta/windmill-data /opt/windmill-pasta/lsp-cache
cp /opt/windmill-pizza/.env /opt/windmill-pizza/docker-compose.yml /opt/windmill-pasta/
chmod 600 /opt/windmill-pasta/.env
# Update configuration
sed -i 's/pizza/pasta/g' /opt/windmill-pasta/.env
sed -i 's/windmill.yourdomain.com/pasta.yourdomain.com/g' /opt/windmill-pasta/.env
# Generate new password
NEW_PASSWORD=$(openssl rand -hex 16)
sed -i "s/POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$NEW_PASSWORD/" /opt/windmill-pasta/.env
sed -i "s#postgresql://postgres:[^@]*@#postgresql://postgres:$NEW_PASSWORD@#" /opt/windmill-pasta/.env
# Update docker-compose.yml
sed -i 's/pizza/pasta/g' /opt/windmill-pasta/docker-compose.yml
sed -i 's/windmill.yourdomain.com/pasta.yourdomain.com/g' /opt/windmill-pasta/docker-compose.yml
安全考量
网络隔离
- PostgreSQL 仅可从共享 Docker 网络
proxy上的容器访问;如需更严格的隔离,请使用独立的内部网络 - 不向外部暴露数据库端口
- 在 Traefik 层终止 HTTPS 连接
资源限制
- 工作进程容器有 CPU 和内存限制
- 资源限制可减轻失控任务及资源耗尽尝试造成的影响
- 可根据服务器容量配置
SSL 安全
- 自动获取 Let’s Encrypt 证书
- HTTP 到 HTTPS 重定向
- 现代 TLS 配置
监控与维护
每周健康检查
# Container status
docker compose ps
# Resource usage
docker stats --no-stream
# Log analysis
docker compose logs | grep -i error
每月维护
# Backup database first
cd /opt/windmill-pizza
./backup.sh
# Update containers
./update.sh
# Clean old data
docker system prune -f
成本明细与对比
月度成本
自托管设置:
- Hetzner CX23(4GB RAM):€5.99/月,不含增值税,含一个主 IPv4 地址(截至 2026 年 10 月)
- 域名成本:约 €1/月
- 总计:约 €7/月,不含增值税
Windmill Cloud 对比:
- 云服务:每个开发者席位 $20/月,每位操作员 $10/月(截至 2026 年 10 月)
- 节省:当两名或更多开发者需要席位时,每年节省 $250+
性能优势
自托管优势:
- 不限工作流执行次数
- 无外部速率限制
- 完全控制数据
- 自定义集成
- 灵活扩展资源
故障排查参考
快速诊断
# 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
windmill-worker-2:
image: ${WM_IMAGE}
container_name: windmill-pizza-worker-2
environment:
- DATABASE_URL=${DATABASE_URL}
- MODE=worker
- WORKER_GROUP=heavy
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、不暴露数据库端口和资源限制(挂载 Docker 套接字仍会赋予工作进程主机级访问权限)
- 可扩展:可根据需要轻松添加工作进程和资源
- 私密:您的代码和数据保留在您的基础设施上,除非您的工作流将它们发送到外部服务
此配置已在生产环境中经过测试,提供了业务关键工作流自动化所需的可靠性。故障排查步骤解决了部署过程中遇到的实际问题,特别是影响许多自托管安装的 PostgreSQL 身份验证问题。
对于复杂的工作流需求或企业部署,建议咨询专业人士以优化您的具体使用场景并确保最佳资源分配。
下一步
- 探索适用于 Windmill 的 webhook 故障排查技术
- 查看处理大型数据集的数据载荷优化
- 考虑与现有 n8n 安装的集成
关于 tva
tva 提供数据库系统、云环境和全球供应链的综合基础设施管理。我们系统化的方法将严格的安全协议与性能优化相结合,同时战略咨询服务实现数字能力和实物资产的精准协调——在所有业务中保持最高标准的卓越运营和合规水平。
访问 tva.sg 了解更多关于我们服务和其他自动化教程的信息。