跳过正文
  1. 全部文章/

自建CI/CD:Woodpecker CI + Gitea 完整实践

目录

为什么选 Woodpecker CI
#

如果你已经在用 Gitea 自建 Git 服务,那下一步自然会需要 CI/CD。选项不少:

  • Gitea Actions:内置,语法兼容 GitHub Actions,生态丰富,但 Runner 相对重,配置稍复杂
  • Jenkins:功能强大,但资源占用高,配置复杂,界面老旧
  • Woodpecker CI:从 Drone CI fork 而来,Apache 2.0 开源,与 Gitea 原生集成,Server + Agent 合计空闲内存不到 200MB,YAML 配置简单直观

对个人或小团队自建场景,Woodpecker 是性价比最高的选择。它目前是 Codeberg(隐私友好的 GitHub 替代品)的主力 CI/CD 引擎,社区活跃,2026 年仍在积极维护,最新稳定版 v3.13.0。


架构概览
#

Woodpecker 由两个组件构成:

graph LR
    Gitea["Gitea
代码仓库"] -->|webhook| Server["Woodpecker Server
:8000 管理界面
:9000 Agent 通信"] Server -->|任务分发| Agent["Woodpecker Agent"] Agent -->|启动临时容器| Container["Docker 容器
执行 Pipeline 步骤"] Container -->|执行完毕| Destroy["容器销毁
环境干净"]

Server:调度中心,提供 Web 管理界面,接收 Gitea webhook,分发任务。

Agent:执行者,主动连接 Server(单向),拉取任务,在 Docker 容器里执行 pipeline 步骤,执行完容器自动销毁。

关键点:Agent 主动连接 Server,不是反过来。这意味着远程 Agent 不需要开放任何入站端口,只要能访问 Server 的 9000 端口即可,部署极其灵活。


部署前准备
#

1. Gitea 创建 OAuth 应用
#

登录 Gitea → 右上角头像 → SettingsApplicationsManage OAuth2 Applications

填写:

  • Application Name:Woodpecker
  • Redirect URIhttps://ci.yourdomain.com/authorize

创建后保存好 Client IDClient Secret,只显示一次。

2. 允许本地 Webhook(同机部署必须)
#

如果 Gitea 和 Woodpecker 在同一台机器,需要在 Gitea 配置文件(通常是 /etc/gitea/conf/app.ini 或 Docker volume 内)添加:

1
2
[webhook]
ALLOWED_HOST_LIST=*

否则 Gitea 会拒绝向本机地址发送 webhook,pipeline 永远无法触发。

3. 生成 Agent Secret
#

1
2
openssl rand -hex 32
# 或者任意的复杂长度复杂token

Server 和 Agent 共用这个值做身份验证,妥善保存。


部署:两个独立的 Docker Compose
#

推荐 Server 和 Agent 分开放在不同目录,职责清晰,独立升级。同时版本号最好一致,避免出现问题。

Server:/opt/woodpecker-server/
#

.env 文件(敏感信息单独存放,不进 compose 文件):

1
2
3
4
5
6
WOODPECKER_HOST=https://ci.yourdomain.com
WOODPECKER_GITEA_URL=https://git.yourdomain.com
WOODPECKER_GITEA_CLIENT=你的ClientID
WOODPECKER_GITEA_SECRET=你的ClientSecret
WOODPECKER_AGENT_SECRET=上面生成的随机字符串
WOODPECKER_ADMIN=你的Gitea用户名

docker-compose.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
services:
  woodpecker-server:
    image: woodpeckerci/woodpecker-server:v3.13.0
    container_name: woodpecker-server
    restart: unless-stopped
    ports:
      - "127.0.0.1:8000:8000"     # 只本机反向代理访问
      - "10.0.4.39:9000:9000"     # 只内网 Agent 访问,如果开放公网访问,最好配置防火墙只允许Agent客户端访问
    volumes:
      - woodpecker-server-data:/var/lib/woodpecker/
    environment:
      - WOODPECKER_OPEN=false
      - WOODPECKER_HOST=${WOODPECKER_HOST}
      - WOODPECKER_GITEA=true
      - WOODPECKER_GITEA_URL=${WOODPECKER_GITEA_URL}
      - WOODPECKER_GITEA_CLIENT=${WOODPECKER_GITEA_CLIENT}
      - WOODPECKER_GITEA_SECRET=${WOODPECKER_GITEA_SECRET}
      - WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
      - WOODPECKER_ADMIN=${WOODPECKER_ADMIN}
    networks:
      - 1panel-network

volumes:
  woodpecker-server-data:

networks:
  1panel-network:
    external: true

Agent:/opt/woodpecker-agent/
#

.env 文件

1
2
WOODPECKER_SERVER=woodpecker-server:9000
WOODPECKER_AGENT_SECRET=和Server一样的值

docker-compose.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
services:
  woodpecker-agent:
    image: woodpeckerci/woodpecker-agent:v3.13.0
    container_name: woodpecker-agent
    hostname: main-agent          # Agent 显示名称
    restart: unless-stopped
    volumes:
      - woodpecker-agent-config:/etc/woodpecker
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - WOODPECKER_SERVER=${WOODPECKER_SERVER}
      - WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
      - WOODPECKER_MAX_WORKFLOWS=2
      - WOODPECKER_LABELS=location=main
    networks:
      - 1panel-network

volumes:
  woodpecker-agent-config:

networks:
  1panel-network:
    external: true

启动顺序
#

1
2
3
4
5
6
7
8
9
# 先启动 Server
cd /opt/woodpecker-server && docker compose up -d

# 再启动 Agent
cd /opt/woodpecker-agent && docker compose up -d

# 检查日志
docker logs -f woodpecker-server
docker logs -f woodpecker-agent

反向代理配置
#

管理界面需要通过反向代理对外提供 HTTPS 访问。

Caddy(推荐,自动证书)

1
2
3
ci.yourdomain.com {
    reverse_proxy localhost:8000
}

Nginx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
server {
    listen 443 ssl;
    server_name ci.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/ci.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ci.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

激活仓库
#

这一步很多人会忽略,导致 Gitea 里看不到 webhook。

Webhook 不是自动注册的,必须在 Woodpecker 里手动激活仓库:

  1. 登录 https://ci.yourdomain.com,用 Gitea OAuth 授权
  2. 点击左上角 Repositories+
  3. 找到目标仓库,点右边的开关激活

激活后 Woodpecker 才会去 Gitea 注册 webhook。去 Gitea 仓库 → Settings → Webhooks 验证是否成功注册。


第一条 Pipeline
#

在仓库根目录创建 .woodpecker.yml,推送后自动触发:

1
2
3
4
5
6
steps:
  - name: hello
    image: alpine
    commands:
      - echo "Woodpecker is working!"
      - date

推送后在 Woodpecker 管理界面能看到 pipeline 运行状态和日志。


关键坑总结
#

坑 1:latest tag 已废弃
#

官方已明确废弃 latest tag,用 v3.13.0 这样的精确版本。

更重要的是:Server 和 Agent 版本必须完全一致,版本不匹配会导致 gRPC 协议不兼容,Agent 无法连接 Server,报错类似 grpc version mismatch

坑 2:WOODPECKER_HOST 必须填对
#

这个值用于两个关键用途:向 Gitea 注册的 webhook 回调地址,以及 OAuth 登录的回调地址。

填错或填内网地址会导致 webhook 打不到 Woodpecker,pipeline 永远不会触发。必须是公网可访问的完整地址,格式 https://ci.yourdomain.com,结尾不加斜杠。

坑 3:8000 和 9000 端口的暴露策略
#

  • 8000(管理界面):有反向代理的情况下绑定 127.0.0.1:8000,不对外暴露,走反向代理的 443
  • 9000(Agent gRPC):只绑定内网 IP,绝对不要直接对公网暴露,Agent 通过内网或 VPN 连接

坑 4:Gitea 和 Woodpecker 同机的网络问题
#

Agent clone 代码时用的是 Gitea API 返回的 URL,如果两者都在 Docker 里,Agent 容器必须加入 Gitea 所在的 Docker 网络,否则 clone 步骤会失败。

查看 Gitea 容器所在网络:

1
docker inspect gitea --format '{{ "{{" }}range $k,$v := .NetworkSettings.Networks{{ "}}" }}{{ "{{" }}$k{{ "}}" }}{{ "{{" }}end{{ "}}" }}'

然后在 Agent 的 compose 里加入这个网络,或设置:

1
2
environment:
  - WOODPECKER_BACKEND_DOCKER_NETWORK=gitea的网络名

坑 5:Cloudflare 不兼容 gRPC 长连接
#

如果域名走了 Cloudflare 橙云代理,Agent 的 gRPC 连接会超时断开。

解决方案:Agent 通信域名在 Cloudflare 设为 DNS Only(灰云),或让 Agent 直接连内网 IP,管理界面可以继续走 Cloudflare。


远程 Agent 部署
#

得益于 Agent 主动连接的设计,在任意远程服务器部署 Agent 非常简单。远程 Agent 需要访问 Server 的 9000 端口,推荐通过 WireGuard 等 VPN 组网连接内网 IP;如果必须走公网,务必用防火墙白名单限制来源 IP(参考上面坑 3)。

1
2
3
4
5
6
7
8
9
docker run -d \
  --name woodpecker-agent \
  --restart unless-stopped \
  -e WOODPECKER_SERVER=ci.yourdomain.com:9000 \
  -e WOODPECKER_AGENT_SECRET=你的secret \
  -e WOODPECKER_HOSTNAME=agent-hongkong \
  -e WOODPECKER_LABELS="location=hk" \
  -v /var/run/docker.sock:/var/run/docker.sock \
  woodpeckerci/woodpecker-agent:v3.13.0

在 pipeline 里用 label 指定在哪台 Agent 上执行:

1
2
3
4
5
6
7
8
labels:
  location: hk

steps:
  - name: deploy
    image: alpine
    commands:
      - ./deploy.sh

集成 Trivy 安全扫描
#

在 pipeline 里直接加入 Trivy,推送自动扫描,PR 时严格卡漏洞:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
steps:
  # 日常 push:扫描但不阻断,只看日志
  - name: trivy-scan
    image: aquasec/trivy
    commands:
      - trivy fs --severity HIGH,CRITICAL --exit-code 0 .
    when:
      event: push

  # PR 时:发现高危直接失败,阻止合并
  - name: trivy-strict
    image: aquasec/trivy
    commands:
      - trivy fs --severity HIGH,CRITICAL --exit-code 1 .
    when:
      event: pull_request

无需额外部署任何服务,直接跑容器,扫完自动销毁。


结语
#

Woodpecker CI + Gitea 是目前自建 CI/CD 最轻量、最易维护的组合之一。整套系统空闲资源占用不到 300MB,零外部依赖,配置跟随代码走,数据完全在自己手里。

官方文档覆盖了所有配置项,但这篇文章希望补充官方文档没有强调的决策原因和实际踩坑。如果你按照这篇文章部署遇到问题,大概率是以下几个地方之一:Gitea webhook 本地访问限制、网络不通、版本不一致、WOODPECKER_HOST 填错。

相关文章