3919 字
20 分钟
Woodpecker CI 搭配 Gitea 本地局域网 CI/CD 完全指南

为什么需要自托管 CI/CD#

代码写完只是第一步。测试、构建、部署——这些重复性劳动不该手工执行。CI/CD 解决的核心问题:

  • 自动化测试:每次推送代码自动跑测试,避免”在我机器上能跑”的尴尬
  • 自动构建:代码合并后自动构建 Docker 镜像、静态资源
  • 自动部署:通过 main 分支更新触发自动部署到服务器
  • 完全自控:代码和数据都在自己设备上,不依赖 GitHub Actions 等第三方服务

Woodpecker CI 是 Drone CI 的开源 fork,专为 Gitea/Forgejo 等轻量级 Git 平台设计。自 2022 年从 Drone 分叉以来,Woodpecker 已成为自托管 CI/CD 领域的首选方案之一——轻量、配置简单、资源占用低,非常适合内网环境。

Woodpecker 最新稳定版为 v3.14.1(2026-05-12 发布)。请查阅 GitHub Releases 确认当前版本号。

Woodpecker CI 是什么#

Woodpecker CI 是 Go 语言编写的轻量级 CI/CD 系统,核心特点:

特性说明
原生 Gitea/Forgejo 集成OAuth2 登录,自动 Webhook,无缝对接
Server + Agent 架构Server 负责 Web UI 和调度,Agent 负责执行 Pipeline
容器化执行每个构建步骤在独立 Docker 容器中运行,环境隔离
YAML 配置即代码.woodpecker.yml 文件定义 Pipeline,纳入版本管理
插件生态复用 Drone 兼容插件,支持 Docker 构建、通知、部署等
资源占用低Server 内存 ~50 MB,Agent 内存 ~30 MB
CLI 工具woodpecker-cli 支持本地调试 Pipeline(v3.0+ cli exec

与同类工具对比#

特性Woodpecker CIDrone CIJenkinsGitea Actions
开源协议Apache 2.0部分功能闭源MITMIT
资源占用
Gitea 集成原生原生需插件内置
容器隔离否(进程级)是(DinD)
配置复杂度
插件生态兼容 Drone 插件丰富极丰富有限
维护状态活跃开源社区企业化运营老牌项目Gitea 官方

对于自托管 Gitea 用户,Woodpecker CI 和 Gitea Actions 是两个最直接的选择。Gitea Actions 胜在零额外部署(Gitea 内置 Runner),但 Woodpecker CI 提供更成熟的 Pipeline 语法、Drone 插件兼容和更好的调度控制。

系统要求#

项目最低要求推荐配置
CPU1 核2 核+
内存512 MB(Server + Agent)1 GB+
磁盘2 GB(SQLite 数据库 + 构建缓存)10 GB+
Docker≥ 20.10最新稳定版
Gitea≥ 1.18(OAuth2 支持)最新稳定版(1.26.2,2026-05-20 发布)

Server 本身极为轻量(~50 MB 内存),资源消耗大头在 Agent 执行的构建任务。如果同一台机器上跑多个并发构建(如多平台 Docker 镜像),建议 4 GB+ 内存和 4 核以上 CPU。

生产环境数据库建议#

Woodpecker Server 默认使用 SQLite 存储元数据(Pipeline 历史、仓库列表、用户信息)。SQLite 适合个人或小团队使用(< 50 个仓库、Pipeline 历史较少)。当仓库数量增多或 Pipeline 历史累积后,建议切换到 PostgreSQL:

场景推荐数据库
个人/小团队(< 50 仓库)SQLite(默认,零配置)
团队/生产环境PostgreSQL 16+

使用 PostgreSQL 只需在 Server 容器中配置环境变量:

environment:
- WOODPECKER_DATABASE_DRIVER=postgres
- WOODPECKER_DATABASE_DATASOURCE=postgres://user:password@postgres:5432/woodpecker?sslmode=disable

SQLite 和 PostgreSQL 之间不能直接迁移数据。建议一开始就评估需求,避免后期迁移。

架构概览#

graph LR
User[开发者 Push 代码] --> Gitea[Gitea 代码仓库]
Gitea -->|Webhook| WoodpeckerServer[Woodpecker Server]
WoodpeckerServer -->|gRPC :9000| WoodpeckerAgent[Woodpecker Agent]
WoodpeckerAgent -->|Docker API| DockerContainers[构建容器]
WoodpeckerAgent -->|git clone| Gitea

关键通信路径:

  • 用户 → Woodpecker Server (:8000):Web UI 和 API
  • Gitea → Woodpecker Server (:8000):Webhook 事件推送
  • Woodpecker Server → Agent (:9000):gRPC,任务下发和日志回传
  • Agent → Gitea:git clone 拉取代码,需在同一 Docker 网络内

Agent 拉取代码依赖容器间网络互通。构建容器内必须能解析 Gitea 主机名——可通过加入同一 Docker 网络、使用 docker0 网关 IP 或 host 网络模式实现。推荐做法是将 Gitea 和 Agent 放入同一自定义网络。

Docker Compose 部署#

前提条件#

  • Gitea 已部署运行(假设地址为 http://192.168.1.100:3000,服务名为 gitea
  • Docker 和 Docker Compose 已安装
  • Gitea 容器所在 Docker 网络已知(如 gitea_default 或自定义网络)

Step 1 — 创建 Gitea OAuth2 应用#

在 Gitea 中创建 OAuth2 应用。有两种方式,任选其一:

  • 用户自建(推荐,仅授权个人仓库):Gitea → 头像 → 设置 → 应用 → 创建新的 OAuth2 应用程序
  • 管理员全局创建(授权所有用户仓库):Gitea → 站点管理 → 应用 → 创建新的 OAuth2 应用程序
字段
应用名称Woodpecker CI
重定向 URIhttp://192.168.1.100:8000/authorize

回调 URI 的协议和主机名必须与 WOODPECKER_HOST 完全一致。如果通过 Nginx 反代使用 HTTPS,URI 也应改为 https://your-domain/authorize

  1. 创建后获取 客户端 ID客户端密钥,后续配置需要。

Step 2 — 配置 Gitea 允许本地 Webhook#

如果 Gitea 和 Woodpecker 在同一台服务器上(默认 Docker 网络),Gitea 默认禁止向本地地址发送 Webhook。编辑 Gitea 的 app.ini

[webhook]
ALLOWED_HOST_LIST = external,loopback

Step 3 — 生成 Agent 共享密钥#

Terminal window
openssl rand -hex 32

复制生成的字符串,Server 和 Agent 两边需一致。

Step 4 — Docker Compose#

创建目录和配置文件:

Terminal window
mkdir -p /opt/woodpecker && cd /opt/woodpecker
docker-compose.yml
services:
woodpecker-server:
image: woodpeckerci/woodpecker-server:v3
container_name: woodpecker-server
restart: unless-stopped
ports:
- "8000:8000" # Web UI + API
- "9000:9000" # gRPC (Agent 连接)
volumes:
- ./woodpecker-data:/var/lib/woodpecker/
environment:
- WOODPECKER_OPEN=true
- WOODPECKER_HOST=http://192.168.1.100:8000
- WOODPECKER_GITEA=true
- WOODPECKER_GITEA_URL=http://192.168.1.100:3000
- WOODPECKER_GITEA_CLIENT=4f8a7b2c-xxxx-xxxx-xxxx-xxxxxxxxxxxx
- WOODPECKER_GITEA_SECRET=gto_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- WOODPECKER_AGENT_SECRET=<上一步生成的共享密钥>
- WOODPECKER_ADMIN=your-gitea-username
networks:
- cicd
woodpecker-agent:
image: woodpeckerci/woodpecker-agent:v3
container_name: woodpecker-agent
restart: unless-stopped
command: agent
depends_on:
- woodpecker-server
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WOODPECKER_SERVER=woodpecker-server:9000
- WOODPECKER_AGENT_SECRET=<同上共享密钥>
# - WOODPECKER_MAX_WORKFLOWS=2 # 默认 1,根据 CPU/内存调整
- WOODPECKER_BACKEND_DOCKER_NETWORK=cicd
networks:
- cicd
networks:
cicd:
name: cicd
external: true # 需预先创建:docker network create cicd
volumes:
woodpecker-data:

使用显式自定义网络 cicd,确保 Gitea 也加入同一网络:docker network connect cicd gitea。构建容器内必须能解析 Gitea 主机名(容器名或 LAN IP),否则 git clone 会 DNS 解析失败。如有多个网络,用逗号分隔 WOODPECKER_BACKEND_DOCKER_NETWORK

Step 5 — 启动并验证#

Terminal window
docker compose up -d

查看日志确认无报错:

Terminal window
docker compose logs woodpecker-server
docker compose logs woodpecker-agent

访问 http://192.168.1.100:8000,点击 Login,会自动跳转到 Gitea 的 OAuth 授权页面。授权后回到 Woodpecker 仪表盘。

首次登录后#

  • 左侧 Repositories 列表为空白,手动点 Add repository 搜索并启用仓库
  • 启用的仓库会自动在 Gitea 中创建 Webhook
  • 之后 Push 代码即自动触发 Pipeline

Agent 注册机制(v3+)#

Woodpecker v3 支持用户级和组织级 Agent 注册,作为安全增强选项:

  • 全局 Agent(默认):Server 级别共享,任意仓库的 Pipeline 均可使用
  • 用户级 Agent:仅处理特定用户的仓库任务
  • 组织级 Agent:仅处理特定 Gitea 组织的仓库任务,适合多团队隔离

启用组织级 Agent 注册,在 Server 环境变量中添加:

environment:
- WOODPECKER_AGENT_REGISTRATION_ENABLED=true

然后在 Agent 端指定归属:

environment:
- WOODPECKER_AGENT_LABELS=org=my-team

用户/组织级 Agent 需要用户在 Woodpecker UI 中手动创建 Agent 令牌并填入 Agent 配置。全局 Agent 只需共享密钥即可自动注册。

Rootless 镜像支持#

Woodpecker 官方提供 -rootless 后缀的镜像变体,以非 root 用户运行,适合安全敏感环境:

services:
woodpecker-agent:
image: woodpeckerci/woodpecker-agent:v3-rootless
# ...

Rootless Agent 的 Docker socket 访问需配合 rootless Dockerdocker-socket-proxy 使用。

Pipeline 语法基础#

Pipeline 定义在仓库根目录的 .woodpecker.yml(单位置)或 .woodpecker/ 目录下(多工作流拆分)。

最简示例#

.woodpecker.yml
steps:
- name: test
image: node:22-alpine
commands:
- npm install
- npm test

每个 step 在独立容器中运行,/woodpecker 路径自动挂载为工作目录,步骤间共享。

触发条件:when#

控制哪些事件触发 Pipeline:

when:
- event: push
branch: main
- event: pull_request
- event: tag
- event: cron
cron: nightly
事件触发时机
push代码推送到仓库
pull_request创建或更新 PR
tag创建 Git 标签
cron定时器触发(在 Woodpecker UI 中配置)
manual在 UI 中手动点击运行
deployment部署事件触发

多工作流拆分#

大型项目建议拆分工作流文件,放在 .woodpecker/ 目录:

.woodpecker/
├── lint.yaml # 代码检查
├── test.yaml # 单元测试
├── build.yaml # 构建 Docker 镜像
└── deploy.yaml # 部署到服务器

通过 depends_on 控制执行依赖:

.woodpecker/deploy.yaml
depends_on:
- lint
- test
- build
steps:
- name: deploy
image: debian:stable-slim
commands:
- echo "deploying..."

矩阵构建#

同一 Pipeline 在不同环境下并行执行:

matrix:
GO_VERSION:
- 1.22
- 1.23
PLATFORM:
- linux/amd64
- linux/arm64
steps:
- name: build
image: golang:${GO_VERSION}
commands:
- go build -o app .
environment:
- GOOS=${PLATFORM%%/*}
- GOARCH=${PLATFORM##*/}

依赖服务(services)#

当测试需要数据库、缓存等外部服务时,用 services: 声明随 Pipeline 生命周期启动/销毁的辅助容器:

.woodpecker.yml
services:
postgres:
image: postgres:16-alpine
environment:
- POSTGRES_USER=test
- POSTGRES_PASSWORD=test
- POSTGRES_DB=testdb
redis:
image: redis:7-alpine
steps:
- name: test
image: node:22-alpine
commands:
- npm ci
- npm test
environment:
- DATABASE_URL=postgres://test:test@postgres:5432/testdb
- REDIS_URL=redis://redis:6379

services: 中容器的主机名即服务名(如 postgresredis),可直接在步骤的环境变量中引用。Pipeline 结束后这些容器自动销毁。

密钥管理#

Woodpecker v3 使用 from_secret 语法(旧版 secrets: 关键字已移除):

添加密钥#

在 Woodpecker UI → 仓库 → Settings → Secrets 中添加密钥(如 docker_usernamedocker_password)。

在 Pipeline 中使用#

steps:
- name: deploy
image: woodpeckerci/plugin-docker-buildx
settings:
repo: registry.example.com/my-app
registry: registry.example.com
tags: latest
username:
from_secret: docker_username
password:
from_secret: docker_password

环境变量方式:

steps:
- name: publish
image: node:22-alpine
commands:
- echo "//registry.npmjs.org/:_authToken=$${NPM_TOKEN}" > .npmrc
- npm publish
environment:
NPM_TOKEN:
from_secret: npm_token

命令中使用 $$(双美元符)转义变量,防止 Woodpecker 前端预处理时替换。单 $ 会在 YAML 解析阶段被替换为空(未定义的 YAML 变量),导致最终命令中变量消失。

密钥安全要点#

  • PR 保护:默认 PR 事件不注入密钥,防止恶意 PR 泄露凭据
  • 镜像过滤:可限制密钥仅特定插件可用,防止泄露给不受信任的容器
  • 全局密钥:在 Woodpecker Server 级别添加的密钥对所有仓库可见,慎用

实战示例#

示例 1:前端项目构建部署#

场景:构建静态站点,通过 rsync 同步到服务器。

.woodpecker.yml
steps:
- name: build
image: node:22-alpine
commands:
- npm ci
- npm run build
- name: deploy
image: debian:stable-slim
commands:
- apt-get update && apt-get install -y rsync openssh-client
- mkdir -p ~/.ssh && echo "$${SSH_KEY}" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa
- rsync -avz -e "ssh -o StrictHostKeyChecking=no" dist/ user@192.168.1.10:/var/www/html/
environment:
SSH_KEY:
from_secret: deploy_ssh_key
when:
event: push
branch: main

示例 2:Go 项目测试 + 构建 + Docker 镜像#

场景:Go 后端项目,自动测试、交叉编译、构建多架构 Docker 镜像。

.woodpecker.yml
matrix:
GO_VERSION:
- 1.22
- 1.23
steps:
- name: test
image: golang:${GO_VERSION}
commands:
- go vet ./...
- go test -race -coverprofile=coverage.out ./...
- go tool cover -func=coverage.out
- name: build
image: golang:${GO_VERSION}
commands:
- CGO_ENABLED=0 go build -ldflags="-s -w" -o app .
- name: publish-image
image: woodpeckerci/plugin-docker-buildx
settings:
repo: registry.example.com/myapp
registry: registry.example.com
dockerfile: Dockerfile
platforms: linux/amd64,linux/arm64
tags: latest
auto_tag: true
username:
from_secret: registry_user
password:
from_secret: registry_pass
when:
event: push
branch: main

auto_tag: true 根据 Git 标签自动生成 semver 标签。推送 v1.2.3 标签时,同时打上 v1.2.3v1.2v1 三个标签。

示例 3:Cron 定时任务#

场景:每天凌晨 3 点备份数据库。

在 Woodpecker UI → 仓库 → Settings → Cron Jobs 中添加名为 daily-backup 的定时器,Cron 表达式 0 3 * * *

.woodpecker/backup.yaml
when:
- event: cron
cron: daily-backup
steps:
- name: backup-db
image: postgres:16-alpine
commands:
- pg_dump -h $$DB_HOST -U $$DB_USER $$DB_NAME | gzip > backup-$(date +%Y%m%d).sql.gz
environment:
DB_HOST:
from_secret: db_host
DB_USER:
from_secret: db_user
DB_NAME:
from_secret: db_name
PGPASSWORD:
from_secret: db_password

Nginx 反向代理#

生产环境建议将 Woodpecker 放在 Nginx 反代后面,启用 HTTPS。

Web UI 反代(端口 8000)#

/etc/nginx/sites-available/woodpecker
server {
listen 443 ssl;
server_name ci.example.com;
ssl_certificate /etc/ssl/certs/ci.example.com.pem;
ssl_certificate_key /etc/ssl/private/ci.example.com.key;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket 支持(日志实时推流需要)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}

gRPC 反代(端口 9000,Agent 与 Server 分离时)#

当 Agent 部署在另一台机器、需要通过公网或跨网段连接 Server 时,需反代 gRPC 端口。gRPC 要求 HTTP/2 且必须端到端 TLS:

/etc/nginx/sites-available/woodpecker-grpc
server {
listen 443 ssl http2;
server_name ci-grpc.example.com;
ssl_certificate /etc/ssl/certs/ci-grpc.example.com.pem;
ssl_certificate_key /etc/ssl/private/ci-grpc.example.com.key;
location / {
grpc_pass grpc://127.0.0.1:9000;
}
}

Agent 端对应配置:

environment:
- WOODPECKER_SERVER=ci-grpc.example.com:443
- WOODPECKER_GRPC_SECURE=true

如果 Agent 和 Server 在同一台机器或同一 Docker 网络内,无需反代 9000 端口,Agent 直接连 woodpecker-server:9000 即可。

本地调试 Pipeline(cli exec)#

Woodpecker v3.0+ 支持 cli exec——无需推送到仓库也能在本地跑 Pipeline,调试效率大幅提升。

安装 CLI#

Terminal window
# 从 GitHub Releases 下载
curl -L https://github.com/woodpecker-ci/woodpecker/releases/download/v3.x.x/woodpecker-cli_linux_amd64 -o /usr/local/bin/woodpecker-cli
chmod +x /usr/local/bin/woodpecker-cli

使用方法#

  1. 在 Woodpecker UI 中,进入某个 Pipeline 的运行详情
  2. 点击 Download metadata 下载 JSON 元数据文件
  3. 创建密钥文件(不要直接在命令行传密钥,避免泄露到 shell 历史):
Terminal window
# secrets.env(不要提交到 Git)
docker_username=admin
docker_password=xxx
  1. 本地执行:
Terminal window
woodpecker-cli exec \
--local \
--metadata-file=metadata.json \
--secrets-file=secrets.env

密钥不包含在元数据文件中,需通过 --secrets-file(推荐)或 --secrets 手动传入。--secrets="key=val" 方式会将密钥暴露在 shell 历史和 ps 输出中,仅用于临时调试。

数据备份#

Woodpecker Server 默认使用 SQLite 存储元数据(仓库列表、Pipeline 历史、用户信息),数据库位于 woodpecker-data/woodpecker.db

Terminal window
# 在线备份
docker exec woodpecker-server sqlite3 /var/lib/woodpecker/woodpecker.db ".dump" | gzip > \
/backup/woodpecker/woodpecker-$(date +%Y%m%d-%H%M).sql.gz

Cron 定时备份脚本:

/opt/scripts/backup-woodpecker.sh
#!/bin/bash
BACKUP_DIR="/backup/woodpecker"
RETENTION_DAYS=14
mkdir -p "$BACKUP_DIR"
docker exec woodpecker-server sqlite3 /var/lib/woodpecker/woodpecker.db ".dump" | gzip > \
"$BACKUP_DIR/woodpecker-$(date +%Y%m%d-%H%M).sql.gz"
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete
echo "Backup completed: $(date)"
Terminal window
0 5 * * * /opt/scripts/backup-woodpecker.sh >> /var/log/woodpecker-backup.log 2>&1

Woodpecker 本身的数据库损坏不影响 Gitea 代码,最坏情况重新部署并激活仓库即可。但 Pipeline 历史记录和密钥会丢失,建议定期备份。

常见问题#

Q:登录后仓库列表为空

检查 Gitea OAuth2 回调地址是否与 WOODPECKER_HOST 完全一致(协议、域名、端口均需匹配)。点击 Add repository 搜索,确认 Gitea 用户有对应仓库的读写权限。

Q:Pipeline 不触发,Webhook 未送达

排查步骤:

  1. Gitea 中检查仓库 → 设置 → Webhooks,确认 Woodpecker Webhook 存在且状态正常
  2. 查看 Webhook 最近投递记录,错误 dial tcp 192.168.1.100:8000: connect: connection refused 说明 Gitea 无法访问 Woodpecker 的 8000 端口
  3. Docker 容器间通信:确认 Gitea 和 Woodpecker 在同一 Docker 网络,或使用 host 网络模式
  4. 检查 Gitea app.ini 是否配置了 ALLOWED_HOST_LIST = external,loopback

Q:Webhook 投递成功(HTTP 200),但 Pipeline 不触发

Webhook 正常送达但 Woodpecker 没有创建 Pipeline,常见原因:

  1. 事件过滤不匹配:检查 .woodpecker.yml 中的 when: 条件。例如配置了 branch: main 但推送到了 dev 分支,或配置了 event: push 但当前操作为创建 Tag
  2. 仓库未在 Woodpecker 中激活:前往 Woodpecker UI → Repositories,确认仓库状态为 “Active”。如果仓库被停用或未激活,Webhook 会被忽略
  3. 配置文件路径错误:确认 .woodpecker.yml 在仓库根目录,或工作流文件在 .woodpecker/ 目录下。文件名必须以 .yml.yaml 结尾
  4. YAML 语法错误:Pipeline 配置文件存在 YAML 语法错误时会被静默跳过。用 yamllint 或在 Woodpecker UI 中手动触发一次 Pipeline 以查看错误消息

排查时查看 Woodpecker Server 日志:

Terminal window
docker compose logs woodpecker-server | grep -i "pipeline\|error\|webhook"

Q:构建容器内 git clone 失败,DNS 解析不到 Gitea

Agent 容器需要能解析 Gitea 的主机名。确认:

  • WOODPECKER_BACKEND_DOCKER_NETWORK 包含了 Gitea 所在的 Docker 网络
  • 构建容器内使用与 Agent 相同的 Docker 网络,可解析 Gitea 容器名
Terminal window
# 查看 Gitea 所在网络
docker inspect gitea | grep -A 5 Networks
# 确认 Agent 加入同网络
docker inspect woodpecker-agent | grep -A 5 Networks

Q:Docker 镜像构建失败,提示权限不足

Docker 镜像构建需要特权模式。在 Woodpecker Server 添加环境变量:

environment:
- WOODPECKER_PLUGINS_PRIVILEGED=woodpeckerci/plugin-docker-buildx

Q:from_secret 报错 “secret not found”

  • 确认密钥名称拼写一致(密钥名不区分大小写)
  • 确认密钥已在对应仓库的 Settings → Secrets 中添加
  • 确认 Pipeline 事件不是 pull_request(PR 默认不注入密钥)

Q:环境变量在命令中为空

命令中的 $VARIABLE 在 YAML 解析阶段被视为 YAML 变量,如果未定义则为空字符串。使用 $${VARIABLE} 让 Woodpecker 将 $ 字面传给 Shell。

# 错误
commands:
- echo $MY_VAR # YAML 解析为 echo (空字符串)
# 正确
commands:
- echo $${MY_VAR} # YAML 解析为 echo $MY_VAR,Shell 再展开

参考资源#

Woodpecker CI 搭配 Gitea 本地局域网 CI/CD 完全指南
https://blog.syomega.top/posts/woodpecker-ci-gitea-guide/
作者
酱w
发布于
2026-05-28
许可协议
CC BY-NC-SA 4.0