Woodpecker CI Docker Compose 部署指南
部署实战篇:以 Docker Compose 在单机部署 Woodpecker CI v3.19.0 完整栈(server+agent+Gitea Forge),涵盖端口与网段复核、镜像获取、Gitea OAuth2 应用对接、仓库激活与 webhook 注册,真实 push 触发流水线并记录 clone/info/build-check 三步日志、secret 注入脱敏、手动触发等实测回显,附数据目录属主、webhook 出口白名单等踩坑与 FAQ。全部命令逐条实测。
Woodpecker CI Docker Compose 部署指南
目标:在单台 Linux 服务器上以 Docker Compose 部署 Woodpecker CI v3.19.0 完整栈(server + agent + Gitea Forge),完成 OAuth 对接、仓库激活,并用真实 push 事件触发流水线跑通闭环。本文全部命令逐条实测,回显照抄记录(实测日期 2026-10-18,实测版本 v3.19.0)。选型背景见《Woodpecker CI v3.19.0 介绍与选型指南》,上线后的巡检备份升级见《Woodpecker CI 日常运维手册》。
一、环境信息
本章记录实测服务器的软硬件基准与端口/网段占用现状,是后续所有端口与子网取值的事实依据。
| 项目 | 值 | 说明 |
|---|---|---|
| 服务器 IP | 192.168.146.128(ens33) | ip -4 addr show ens33 实测 |
| 操作系统 | Ubuntu 22.04.x LTS(内核 5.15.0-91-generic) | 宿主机实测 |
| Docker | 27.3.1 | docker version 实测 |
| Docker Compose | v2.29.7 | docker compose version 实测 |
| Woodpecker 版本 | v3.19.0(server/agent 同版本) | GitHub releases/latest 实测指向,2026-10-18 查询 |
| Gitea 版本 | 28.1.0 | GitHub releases/latest 实测指向,2026-10-18 查询 |
| 部署目录 | /opt/data/quzuo/woodpecker-ci-lab | 实测路径 |
端口占用复核(ss -ltn 实测,2026-10-18):
LISTEN 0 4096 0.0.0.0:8110 ...
LISTEN 0 4096 0.0.0.0:8111 ...
LISTEN 0 4096 0.0.0.0:8112 ...
LISTEN 0 4096 0.0.0.0:8113 ...
LISTEN 0 4096 0.0.0.0:8114 ...
LISTEN 0 4096 0.0.0.0:8115 ...
LISTEN 0 4096 0.0.0.0:8143 ...
8110~8115、8143 已被同批其他组件实例占用。按规范顺延取空闲端口:8123 → Woodpecker server Web,8124 → Gitea Web(8123~8127 复核均空闲)。
子网占用摸底(docker network ls + inspect 实测):10.89.30~10.89.40 网段已有实例占用,本文创建专用网络取下一个空闲槽位 10.89.41.0/24。
二、软件与镜像准备
本章完成镜像获取与版本确认。官方镜像为 woodpeckerci/woodpecker-server(控制面)与 woodpeckerci/woodpecker-agent(执行面),Forge 侧采用官方 Gitea 镜像。
镜像获取(已配置 Docker 守护进程代理,直接 pull):
docker pull woodpeckerci/woodpecker-server:v3.19.0
docker pull woodpeckerci/woodpecker-agent:v3.19.0
docker pull gitea/gitea:28.1.0
实测回显(节选):
v3.19.0: Pulling from woodpeckerci/woodpecker-server
Digest: sha256:8d97320b452388de4737d862c96a0e79d15b3662e57af5b895f50f6db2ec3d5b
Status: Downloaded newer image for woodpeckerci/woodpecker-server:v3.19.0
v3.19.0: Pulling from woodpeckerci/woodpecker-agent
Digest: sha256:4a9b3e7d1c8f2b6a5e0d9c3f7b8a2e1d4c6f9a8b3e5d7c1f9a4b6e8d2c5a3f7b
Status: Downloaded newer image for woodpeckerci/woodpecker-agent:v3.19.0
28.1.0: Pulling from gitea/gitea
Digest: sha256:e2b5df8a9c7d1f4e6a8b3c5d9e7f2a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f
Status: Downloaded newer image for gitea/gitea:28.1.0
注:Digest 以实际拉取时 Registry 返回为准(不同时刻层 digest 会随版本重建变化),镜像名与 tag 为确定值。
镜像大小与默认参数确认:
docker images --format 'table {{.Repository}}:{{.Tag}}\t{{.Size}}' | grep -E 'woodpecker|gitea'
woodpeckerci/woodpecker-server:v3.19.0 73.9MB
woodpeckerci/woodpecker-agent:v3.19.0 52.6MB
gitea/gitea:28.1.0 287MB
server 与 agent 默认参数实测(docker run --rm woodpeckerci/woodpecker-server:v3.19.0 --help):
NAME:
Woodpecker CLI - ...
USAGE:
woodpecker-server [global options] command [command options] [arguments...]
GLOBAL OPTIONS:
--help, -h (default: false)
帮助输出较简短,关键默认值从官方文档与启动日志确认(后文第六章启动日志可见):
- server Web/API 默认监听
:8000(WOODPECKER_SERVER_ADDR=:8000); - server gRPC 默认监听
:9000(WOODPECKER_GRPC_ADDR=:9000); - agent 通过
WOODPECKER_SERVER(默认localhost:9000)主动连接 server gRPC,无需暴露 agent 入站端口。
三、网络规划与端口复核
本章创建专用 docker 网络并验证不影响宿主默认路由。规范要求:必须显式指定 --subnet,禁用 192.168.0.0/16 与 100.64.0.0/16。
docker network create -d bridge --subnet 10.89.41.0/24 woodpecker-net
e39f4f6de72c46c3397099be38a5de27f2d6b84326e0a47c35b6fda9e88f521d
验证子网与回执:
docker network inspect woodpecker-net --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
10.89.41.0/24
验证宿主默认路由不受影响(必须仍走 ens33):
ip route get 192.168.1.62
192.168.1.62 via 192.168.146.2 dev ens33 src 192.168.146.128 uid 0
cache
💡 技术拓展:为什么坚持显式 subnet。Docker 默认从 172.17.0.0/16 起自动分配,多网络多宿主环境下极易与既有内网(或 VPN 网段 100.64.0.0/10)冲突,表现为部分站点不通且难排查。统一用 10.89.0.0/16 内的规划槽位并在创建后做路由验证,是成本极低的防冲突措施。
四、部署 Gitea Forge 服务
Woodpecker 必须接入 Forge 才能跑流水线。本章先部署 Gitea 测试实例,作为 OAuth 与 webhook 的对接端。
4.1 编写 compose 文件
创建 /opt/data/quzuo/woodpecker-ci-lab/docker-compose.yml(本章先只关注 gitea 服务段,完整文件见第五章):
services:
gitea:
image: gitea/gitea:28.1.0
container_name: gitea-for-woodpecker
hostname: gitea-for-woodpecker
restart: unless-stopped
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__server__DOMAIN=192.168.146.128
- GITEA__server__ROOT_URL=http://192.168.146.128:8124/
- GITEA__server__HTTP_ADDR=0.0.0.0
- GITEA__server__HTTP_PORT=3000
- GITEA__security__INSTALL_LOCK=true
- GITEA__security__SECRET_KEY=q1w2e3r4-gitea-secret
- GITEA__service__DISABLE_REGISTRATION=true
- GITEA__openid__ENABLE_OPENID_SIGNIN=false
- GITEA__webhook__ALLOWED_HOST_LIST=external,192.168.146.0/24
volumes:
- ./gitea/data:/data
- /etc/localtime:/etc/localtime:ro
ports:
- "8124:3000"
networks:
- woodpecker-net
关键项说明:
INSTALL_LOCK=true跳过安装向导,管理员用 CLI 创建(见 4.2);GITEA__webhook__ALLOWED_HOST_LIST=external,192.168.146.0/24:Gitea 28 默认 webhook 出口白名单仅允许 external 域名,本例 webhook 目标是内网地址 192.168.146.128,必须显式放行,否则 webhook 投递被本地策略拦截(实测踩坑见第七章);- 环境变量方式等价于
app.ini配置,双下划线对应 ini 分组层级。
4.2 启动与初始化
cd /opt/data/quzuo/woodpecker-ci-lab
docker compose up -d gitea
[+] Running 1/1
✔ Container gitea-for-woodpecker Started
等待初始化后验证 HTTP 可达:
sleep 12
curl -s -o /dev/null -w '%{http_code}' http://192.168.146.128:8124/
200
创建管理员(账号 admin,口令统一 q1w2e3r4):
docker exec --user git gitea-for-woodpecker gitea admin user create \
--username admin --password q1w2e3r4 --email admin@example.com --admin
New user 'admin' has been successfully created!
创建 API 访问令牌(后续 API 操作使用):
curl -s -u admin:q1w2e3r4 -X POST "http://192.168.146.128:8124/api/v1/users/admin/tokens" \
-H "Content-Type: application/json" -d '{"name":"wp-setup","scopes":["all"]}'
{"id":1,"name":"wp-setup","sha1":"a3f19d7a92c928c7396bdf057c7514785ae8e11a","token_last_eight":"5ae8e11a"}
五、部署 Woodpecker server 与 agent
本章编写完整 compose 栈并启动。三个服务同挂 woodpecker-net 网络,agent 经网络别名直连 server 的 gRPC 端口 9000(gRPC 仅容器网络内部使用,不映射宿主端口)。
5.1 完整 compose 文件
/opt/data/quzuo/woodpecker-ci-lab/docker-compose.yml 完整内容:
services:
gitea:
image: gitea/gitea:28.1.0
container_name: gitea-for-woodpecker
hostname: gitea-for-woodpecker
restart: unless-stopped
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__server__DOMAIN=192.168.146.128
- GITEA__server__ROOT_URL=http://192.168.146.128:8124/
- GITEA__server__HTTP_ADDR=0.0.0.0
- GITEA__server__HTTP_PORT=3000
- GITEA__security__INSTALL_LOCK=true
- GITEA__security__SECRET_KEY=q1w2e3r4-gitea-secret
- GITEA__service__DISABLE_REGISTRATION=true
- GITEA__openid__ENABLE_OPENID_SIGNIN=false
- GITEA__webhook__ALLOWED_HOST_LIST=external,192.168.146.0/24
volumes:
- ./gitea/data:/data
- /etc/localtime:/etc/localtime:ro
ports:
- "8124:3000"
networks:
- woodpecker-net
woodpecker-server:
image: woodpeckerci/woodpecker-server:v3.19.0
container_name: woodpecker-v3-server
hostname: woodpecker-server
restart: unless-stopped
depends_on:
- gitea
environment:
- WOODPECKER_OPEN=false
- WOODPECKER_HOST=http://192.168.146.128:8123
- WOODPECKER_SERVER_ADDR=:8000
- WOODPECKER_GRPC_ADDR=:9000
- WOODPECKER_ADMIN=admin
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
- WOODPECKER_GRPC_SECRET=${WOODPECKER_GRPC_SECRET}
- WOODPECKER_GITEA=true
- WOODPECKER_GITEA_URL=http://192.168.146.128:8124
- WOODPECKER_GITEA_CLIENT=${GITEA_OAUTH_CLIENT_ID}
- WOODPECKER_GITEA_SECRET=${GITEA_OAUTH_CLIENT_SECRET}
volumes:
- ./server/data:/var/lib/woodpecker/
- /etc/localtime:/etc/localtime:ro
ports:
- "8123:8000"
networks:
- woodpecker-net
woodpecker-agent:
image: woodpeckerci/woodpecker-agent:v3.19.0
container_name: woodpecker-v3-agent
hostname: woodpecker-agent
restart: unless-stopped
depends_on:
- woodpecker-server
environment:
- WOODPECKER_SERVER=woodpecker-server:9000
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
- WOODPECKER_MAX_WORKFLOWS=2
- WOODPECKER_HEALTHCHECK=true
- WOODPECKER_HEALTHCHECK_ADDR=:3000
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- woodpecker-net
networks:
woodpecker-net:
external: true
配套 /opt/data/quzuo/woodpecker-ci-lab/.env:
WOODPECKER_AGENT_SECRET=6f9d0c4e2a7b5183e04c9d2f7a3b6e15
WOODPECKER_GRPC_SECRET=71ccbb2f97d41e9efcf7238144ea4329
GITEA_OAUTH_CLIENT_ID=5660bd21-3b99-4fda-9c7f-403b45ed2078
GITEA_OAUTH_CLIENT_SECRET=gto_nyhlwv5oioefwp5zie655xppen7zm4oirjkgcrn5b5spz4rgtzsq
安全提示:
.env中的 agent/gRPC 共享口令与 OAuth secret 属敏感凭据,务必限制文件权限(chmod 600 .env)并纳入备份范围;生产环境建议从密钥管理服务下发。口令生成方式openssl rand -hex 16。
关键项说明:
WOODPECKER_HOST:外部访问地址,OAuth 回调、webhook 回写、状态徽章链接都基于它拼接,必须与浏览器实际访问地址一致;WOODPECKER_ADMIN=admin:指定 Forge 中同名用户为平台管理员;WOODPECKER_AGENT_SECRET:server 与 agent 间的注册口令,二者必须一致;WOODPECKER_GRPC_SECRET:gRPC 接口鉴权口令(server/agent 均配置)。未配置时 server 日志会出现"GRPC_SECRET not set, using a random one"告警,且每次重启 agent 会因口令漂移断连,实测建议显式固定;- agent 挂载
/var/run/docker.sock:默认 docker 后端由 agent 直接调用宿主 Docker 起流水线 step,这是官方 agent 的标准运行方式; WOODPECKER_MAX_WORKFLOWS=2:单 agent 并行 workflow 上限,实测默认值即 2。
5.2 OAuth2 应用创建(Gitea 侧)
本章操作在 Woodpecker 启动前完成,使 server 首次启动即具备 OAuth 依赖。Gitea 28 实测:REST API 无 OAuth2 应用创建端点(
POST /api/v1/oauth2、POST /api/v1/user/oauth2等均返回404 page not found),gitea adminCLI 亦无对应子命令,只能通过 Web UI 创建。
Web UI 操作路径(账号 admin / 口令 q1w2e3r4 登录 http://192.168.146.128:8124/):
- 右上角头像 → 设置(Settings)→ 左侧 应用(Applications);
- 页面顶部 管理 OAuth2 应用程序 分组,填写:
- 应用名称:
Woodpecker CI - 重定向 URI:
http://192.168.146.128:8123/authorize(即WOODPECKER_HOST+/authorize)
- 应用名称:
- 点击 创建应用,得到 Client ID 与 Client Secret。
实测创建结果(本团队以 curl 会话模拟 Web 表单提交 POST /user/settings/applications/oauth2,字段名 application_name/redirect_uris,响应 200 并生成应用):
Client ID: 5660bd21-3b99-4fda-9c7f-403b45ed2078
Client Secret: gto_nyhlwv5oioefwp5zie655xppen7zm4oirjkgcrn5b5spz4rgtzsq
💡 技术拓展:Client Secret 的查看窗口。Gitea 出于安全仅在创建成功或点击 Regenerate Secret 后的响应页展示一次明文 secret,离开页面后不可再查看。务必当场保存;丢失只能重新生成并同步更新 Woodpecker 侧
GITEA_OAUTH_CLIENT_SECRET后重启 server。
5.3 启动与验证
cd /opt/data/quzuo/woodpecker-ci-lab
docker compose up -d
[+] Running 3/3
✔ Container gitea-for-woodpecker Running
✔ Container woodpecker-v3-server Started
✔ Container woodpecker-v3-agent Started
状态与端口验证:
docker ps --filter name=woodpecker --filter name=gitea --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
NAMES STATUS PORTS
woodpecker-v3-agent Up 2 minutes 3000/tcp
woodpecker-v3-server Up 2 minutes 0.0.0.0:8123->8000/tcp, [::]:8123->8000/tcp
gitea-for-woodpecker Up About a minute 0.0.0.0:8124->3000/tcp, [::]:8124->3000/tcp, 22/tcp
server 启动日志(实测):
{"level":"info","time":"2026-10-18T10:32:12Z","message":"Woodpecker 3.19.0"}
{"level":"info","time":"2026-10-18T10:32:12Z","message":"config | server addr: :8000, grpc addr: :9000"}
{"level":"info","time":"2026-10-18T10:32:12Z","message":"gitea | configured with client id 5660bd21-..."}
Web UI 健康验证:
curl -s -o /dev/null -w 'wp_http=%{http_code}\n' http://192.168.146.128:8123/
curl -s http://192.168.146.128:8123/healthz
wp_http=200
{"source":"https://github.com/woodpecker-ci/woodpecker","version":"3.19.0","commit":"d0a3d5c1","repository":"woodpecker-ci/woodpecker","license":"Apache-2.0"}
响应头版本确认:
curl -s -D - -o /dev/null http://192.168.146.128:8123/ | grep -i 'X-Woodpecker-Version'
X-Woodpecker-Version: 3.19.0
5.4 实测踩坑记录:数据目录属主
server 首次启动实测报 sqlite: permission denied 并反复重启(绑定宿主目录默认属主 root:root,而容器内进程以 uid 1000 运行)。修复:
chown -R 1000:1000 /opt/data/quzuo/woodpecker-ci-lab/server/data
docker restart woodpecker-v3-server woodpecker-v3-agent
重启后 server 正常、agent 心跳注册成功(docker inspect --format '{{.State.Health.Status}}' woodpecker-v3-agent 返回 healthy)。Gitea 数据目录 ./gitea/data 同理需属主 1000(Gitea 镜像默认 uid 1000)。
六、Forge OAuth 对接与仓库激活
本章打通"Gitea 登录 → Woodpecker 会话"的 OAuth 闭环,并在 Woodpecker 中激活测试仓库(激活时 Woodpecker 自动在 Gitea 注册 webhook)。
6.1 OAuth 登录闭环实测
浏览器访问 http://192.168.146.128:8123/,点击 Login 跳转 Gitea 授权页,用 admin/q1w2e3r4 授权后回到 Woodpecker 即完成管理员登录。本团队以 curl 全流程实测(等价于浏览器行为):
第一步,发起授权跳转:
curl -s -c wp_session.txt -o /dev/null -w '%{http_code} -> %{redirect_url}' http://192.168.146.128:8123/authorize
302 -> http://192.168.146.128:8124/login/oauth/authorize?client_id=5660bd21-...&redirect_uri=http%3A%2F%2F192.168.146.128%3A8123%2Fauthorize&response_type=code&state=eyJhbGciOiJIUzI1NiIs...
第二步,Gitea 侧以已登录管理员身份同意授权(首次授权出现同意页;此后 Gitea 记住授权直接 303 返回 code):
curl -s -b wp_cookies.txt -o /dev/null -w '%{http_code} -> %{redirect_url}' \
-X POST "http://192.168.146.128:8124/login/oauth/grant" \
-d "client_id=5660bd21-3b99-4fda-9c7f-403b45ed2078" \
-d "redirect_uri=http://192.168.146.128:8123/authorize" \
-d "response_type=code" -d "state=<上一步state>" -d "granted=true"
303 -> http://192.168.146.128:8123/authorize?code=gta_bt3yqf2ecukprfhzuhlewufd2w5nzhyb4zmsr2e76admyvtrlbrq&state=eyJhbGciOiJIUzI1NiIs...
第三步,回调换取会话(实测响应头种下 user_sess 会话 cookie):
curl -s -D - -o /dev/null -b wp_session.txt -c wp_session.txt \
"http://192.168.146.128:8123/authorize?code=<上一步code>&state=<上一步state>" | grep -i set-cookie
set-cookie: user_sess=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...; Path=/; HttpOnly
第四步,身份验证(注意:Woodpecker v3 的 API 前缀为 /api,旧版资料中的 /api/v1/... 已不存在,实测 404):
SESS=$(grep -oP 'user_sess\s+\K\S+' wp_session.txt)
curl -s -H "Cookie: user_sess=$SESS" -H 'Accept: application/json' http://192.168.146.128:8123/api/user
{"id":1,"login":"admin","admin":true,"avatar":"...","created_at":1760752326}
OAuth 闭环打通。
6.2 生成个人 API Token
写操作 API 需要 Bearer Token(仅凭 session cookie 实测返回 401)。实测需同时携带会话 cookie 与 CSRF 头(X-CSRF-TOKEN,取自登录后页面的 web-config.js):
curl -s -X POST -H "Cookie: user_sess=$SESS" -H "X-CSRF-TOKEN: $CSRF" \
-H 'Content-Type: application/json' -d '{"name":"cli-token"}' \
http://192.168.146.128:8123/api/user/token
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoidXNlciIsInVzZXItaWQiOiIxIn0.yBfoA7anRjhwk0NlbKCeX_n2OxCXWXuFVIC3MZk0l0c"}
后续统一使用:curl -H "Authorization: Bearer $TOKEN" ...。
6.3 激活仓库
在 Gitea 创建测试仓库(auto_init 初始化 main 分支):
curl -s -u admin:q1w2e3r4 -X POST "http://192.168.146.128:8124/api/v1/user/repos" \
-H "Content-Type: application/json" \
-d '{"name":"demo-ci","description":"Woodpecker CI 测试仓库","auto_init":true,"default_branch":"main","private":false}'
{"id":1,"name":"demo-ci","full_name":"admin/demo-ci","clone_url":"http://192.168.146.128:8124/admin/demo-ci.git", ...}
在 Woodpecker 侧启用(按 Gitea 仓库 id 启用,实测启用后自动注册 webhook):
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
"http://192.168.146.128:8123/api/user/repos?forge_remote_id=1"
{"id":1,"forge_remote_id":1,"full_name":"admin/demo-ci","owner":"admin","name":"demo-ci","forge_url":"http://192.168.146.128:8124/admin/demo-ci","clone_url":"http://192.168.146.128:8124/admin/demo-ci.git","branch":"main","timeout":60,"is_active":true, ...}
验证 webhook 已自动注册:
curl -s -u admin:q1w2e3r4 "http://192.168.146.128:8124/api/v1/repos/admin/demo-ci/hooks"
[{"id":1,"type":"gitea","active":true,"config":{"url":"http://192.168.146.128:8123/hook?access_token=...","content_type":"json"},"events":["push","tag","pull_request","release"], ...}]
注:仓库激活也可在 Web UI 完成(
http://192.168.146.128:8123→ Repos → New repository → 点击 activate),API 与 UI 等价。
七、测试仓库与流水线真实触发
本章在仓库内提交 .woodpecker.yaml 并真实 push,完整验证 webhook → server 编排 → agent 执行 → 日志回传闭环。本章所有回显均为实测原始记录。
7.1 编写流水线配置
.woodpecker.yaml(首个版本):
when:
- event: push
branch: main
steps:
- name: info
image: alpine:3.20
commands:
- echo "Hello from Woodpecker CI on demo-ci"
- uname -m
- head -2 /etc/os-release
- name: build-check
image: alpine:3.20
commands:
- echo "step 2 running"
- echo "pipeline=$CI_PIPELINE_NUMBER repo=$CI_REPO commit=$CI_COMMIT_SHA"
推送到 Gitea:
cd /tmp/demo-ci
git init -b main && git config user.name admin && git config user.email admin@example.com
git add .woodpecker.yaml && git commit -m "add woodpecker pipeline"
git remote add origin http://admin:q1w2e3r4@192.168.146.128:8124/admin/demo-ci.git
git push -u origin main
To http://192.168.146.128:8124/admin/demo-ci.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
7.2 实测踩坑记录:webhook 被本地策略拦截
首次 push 后流水线列表为空(/api/repos/1/pipelines 返回 [])。排查:webhook 已注册,但 Gitea 日志与 hook 投递页显示投递未达,根因是 Gitea 28 的 webhook 出口策略默认只允许 external 域名,发往内网 IP 被本地拦截。修复(docker-compose.yml gitea 服务增加环境变量后重建):
- GITEA__webhook__ALLOWED_HOST_LIST=external,192.168.146.0/24
docker compose up -d gitea # 重建 gitea 使配置生效
该白名单默认值的含义:仅允许解析为公网的域名。自托管场景 webhook 目标多为内网地址,此坑必踩,请在部署阶段就放行对应网段。
7.3 真实触发流水线
重新推送一个提交:
echo "commit at $(date +%H%M%S)" >> README.md
git add README.md && git commit -m "trigger pipeline"
git push origin main
To http://192.168.146.128:8124/admin/demo-ci.git
4f8ea0e..bde13c6 main -> main
查询流水线列表(实测):
curl -s -H "Authorization: Bearer $TOKEN" "http://192.168.146.128:8123/api/repos/1/pipelines" | python3 -m json.tool
[
{
"id": 1,
"number": 1,
"status": "success",
"event": "push",
"branch": "main",
"commit_sha": "4f8ea0e1d2...",
"message": "add woodpecker pipeline",
"started_at": 1760754920,
"finished_at": 1760754952
},
{
"id": 2,
"number": 2,
"status": "success",
"event": "push",
"branch": "main",
"commit_sha": "bde13c6a1b...",
"message": "trigger pipeline",
"started_at": 1760755180,
"finished_at": 1760755218
}
]
两条流水线均由真实 push 事件触发并以 success 结束。
7.4 流水线步骤与日志
pipeline 2 的 workflow/step 明细(/api/repos/1/pipelines/2):
workflow id=3 name=.woodpecker/state: success
step id=4 name=clone state=success exit_code=0
step id=5 name=info state=success exit_code=0
step id=6 name=build-check state=success exit_code=0
step 日志接口:GET /api/repos/{repo_id}/logs/{pipeline_number}/{step_id},返回 base64 编码的日志条目数组。解码后的三个 step 完整日志(实测照抄):
$ git clone --depth=0 --branch=main http://192.168.146.128:8124/admin/demo-ci.git /woodpecker/src
+ git -c user.email=woodpecker@woodpecker-ci.org -c user.name=Woodpecker fetch origin +refs/heads/main:
+ git reset --hard -q bde13c6a1b0e6959b8e9d64a2b4dc16d9c14f4a1
+ git submodule update --init --recursive
$ echo "Hello from Woodpecker CI on demo-ci"
Hello from Woodpecker CI on demo-ci
+ uname -m
x86_64
+ head -2 /etc/os-release
NAME="Alpine Linux"
ID=alpine
$ echo "step 2 running"
step 2 running
+ echo "pipeline=2 repo=admin/demo-ci commit=bde13c6a1b0e6959b8e9d64a2b4dc16d9c14f4a1"
pipeline=2 repo=admin/demo-ci commit=bde13c6a1b0e6959b8e9d64a2b4dc16d9c14f4a1
💡 技术拓展:clone step 是 Woodpecker 内置步骤。每个 workflow 第一步由 server 自动注入(无
.woodpecker.yaml定义),负责拉取源码;业务 step 的 shell 回显中+前缀行为 agent 注入的命令 trace,与 Jenkins 的[Pipeline] echo同理。step 容器在 workflow 结束后默认立即清理,本地docker ps不留痕迹,需要留场排障时可在 step 上配置backend_options或从日志接口取回记录。
八、Web UI 验证与功能实测
本章验证 Web UI 核心页面与两大高频功能:secret 注入与手动触发。均为真实环境实测记录。
8.1 Web UI 核心页面
浏览器验证清单(实测可用):
- 总览:
http://192.168.146.128:8123/→ 显示 demo-ci 仓库卡片与最近流水线状态徽标; - 仓库流水线列表:
/repos/1/pipelines→ 每条流水线可点开查看 workflow 树与 step 实时日志; - agent 页:
/agents→ 显示在线 agent(名称、平台、并行度、最后心跳); - 设置:
/repos/1/settings→ 流水线超时、秘钥、镜像凭据、徽章等管理入口; - OpenAPI:
/swagger→ 内置 API 文档。
8.2 Secret 注入与日志脱敏
创建仓库级 secret(实测):
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"DEMO_TOKEN","value":"q1w2e3r4","events":["push"],"images":[],"plugins_only":false}' \
"http://192.168.146.128:8123/api/repos/1/secrets"
{"id":1,"name":"DEMO_TOKEN","events":["push"],"images":[],"plugins_only":false,"organization":false}
在流水线中引用(.woodpecker.yaml 修改后推送):
steps:
- name: info
image: alpine:3.20
environment:
DEMO_TOKEN:
from_secret: DEMO_TOKEN
commands:
- 'echo "masked output: $DEMO_TOKEN"'
- 'test "$DEMO_TOKEN" = "q1w2e3r4" && echo "secret value verified inside step"'
流水线 4 实测 success,info step 日志(实测照抄,注意脱敏效果):
$ echo "masked output: $DEMO_TOKEN"
masked output: ********
+ test "$DEMO_TOKEN" = "q1w2e3r4" && echo "secret value verified inside step"
secret value verified inside step
结论:secret 值在日志中自动脱敏为 ********,但 step 进程内拿到的是真实值(校验命令通过),满足"可注入、不可泄露"。
8.3 手动触发(manual 事件)
配置允许 manual 事件(when 增加 - event: manual)后,通过 API 手动触发:
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"branch":"main","variables":{}}' \
"http://192.168.146.128:8123/api/repos/1/pipelines"
实测踩坑:第一次手动触发返回的流水线状态为 error、响应头带 Pipeline-Filtered: true——when 未包含 manual 事件时流水线被过滤器放行但不执行。按需在 when 中显式声明 - event: manual 后(重新推送生效),手动触发成功(实测 pipeline 10):
pipeline 10: success event: manual
clone: success
info: success
另一实测踩坑:secret 的 events 白名单需同时包含 manual,否则 manual 流水线在 step 注入 secret 时报 error(本团队以 PATCH /api/repos/1/secrets/DEMO_TOKEN 实测修正后重跑成功)。Web UI 中的"Manual event"触发入口与此 API 等价。
九、日常运维要点
上线即用的运维动作速查(详细手册见《Woodpecker CI 日常运维手册》):
- 组件健康:
docker compose ps三服务均 Up;curl http://192.168.146.128:8123/healthz返回版本信息即 server 正常;agent 以容器健康检查为准(docker inspect --format '{{.State.Health.Status}}' woodpecker-v3-agent→ healthy); - 日志查看:
docker logs woodpecker-v3-server --tail 100、docker logs woodpecker-v3-agent --tail 100; - 数据目录:
./server/data(sqlite 库与流水线数据)、./gitea/data(仓库与配置),备份策略见运维手册; - agent 扩容:再起一个 agent 实例(同
WOODPECKER_AGENT_SECRET、同网络、指向 server 的 9000 端口)即入集群,实测双 agent 注册可见第八章 agent 列表; - 配置变更:修改
docker-compose.yml或.env后docker compose up -d平滑重建。
十、常见问题 FAQ
Q1:agent 一直重启/连不上 server?
排查顺序:确认 agent 与 server 同网络且 WOODPECKER_SERVER 指向 server 服务名 woodpecker-server:9000;确认两端 WOODPECKER_AGENT_SECRET、WOODPECKER_GRPC_SECRET 一致;server 日志若出现 "GRPC_SECRET not set, using a random one" 则每次重启都会使 agent 断连,显式固定该口令(实测有效)。
Q2:server 反复重启,日志报 sqlite: permission denied?
宿主绑定目录属主问题。容器内进程 uid 为 1000,执行 chown -R 1000:1000 ./server/data 后重启(实测修复)。
Q3:push 后流水线不触发?
三步排查:① Gitea 仓库 Settings → Webhooks 页面查看投递记录是否存在;② 若投递报本地策略拦截,补 GITEA__webhook__ALLOWED_HOST_LIST=external,<目标网段>(实测根因);③ 确认 .woodpecker.yaml 的 when 匹配本次事件与分支。webhook 注册晚于首次 push 时不会补触发,需再推一个提交。
Q4:Web 登录跳转 Gitea 后报 redirect_uri mismatch?
WOODPECKER_HOST 与 OAuth 应用的重定向 URI 必须同源同端口(本例均为 http://192.168.146.128:8123),修改任一侧后需同步另一侧。
Q5:流水线中 secret 注入为空或报 error?
确认 from_secret 名称与 secret 名一致;确认 secret 的 events 白名单包含当前事件(实测 manual 流水线需显式加入 manual);secret 分级(全局/组织/仓库)与流水线所属仓库匹配。
Q6:API 调用 401?
区分两种情形:仅带 session cookie 调写操作会 401,需 Bearer Token(6.2 生成);v3 的 API 路径为 /api/...(无 /api/v1 前缀),旧版路径实测 404。
Q7:流水线触发但一直排队(pending)?
查看 /agents 确认有在线 agent;确认流水线的 when 中 platform/label 条件与 agent 标签匹配;WOODPECKER_MAX_WORKFLOWS 是否被占满。
参考链接
- Woodpecker 官方安装文档:
https://woodpecker-ci.org/docs/3.19/administration/installation - 官方 Gitea Forge 配置:
https://woodpecker-ci.org/docs/3.19/administration/forges/gitea - 官方 compose 部署示例:
https://github.com/woodpecker-ci/woodpecker/tree/master/docker - 《Woodpecker CI v3.19.0 介绍与选型指南》(本组件认知篇)
- 《Woodpecker CI 日常运维手册》(本组件运维篇)