Gitea/Forgejo 除了轻量还拥有强大的 CI/CD 集成支持,我暂时了解的选择项:
三者都能与 Gitea 轻松集成,最终我选择了 Woodpecker CI。
Woodpecker CI 基本服务组成为 server、agent 及可选的 autoscaler,前两者是必需的,工作流程如下:
┌──────────────────────┐
│ Code Hosting / │ <-- webhooks, events
│ Forge (e.g. Gitea) │ push / pr / commit
└─────────┬────────────┘
│ HTTP / Webhook
▼
┌──────────────────────┐
│ Woodpecker Server │
│ API / Web UI │
│ orchestration │
└─────────┬────────────┘
│ gRPC (queue / job dispatch)
┌────────────┬──────────────┼──────────────┬────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐ (more agents …)
│ Woodpecker │ │ Woodpecker │ │ Woodpecker │
│ Agent (Docker) │ │ Agent (Docker) │ … │ Agent (K8s / │
│ │ │ │ │ Docker / local)│
└────────────────┘ └────────────────┘ └────────────────┘
│ │ │
│ Executes │ Executes │ Executes
│ pipeline steps │ pipeline steps │ pipeline steps
│ (build / test / │ (build / test / │ (build / test /
│ deploy etc.) │ deploy etc.) │ deploy etc.)
▼ ▼ ▼
build logs / status build logs / status build logs / status
└──────────────┬──────────────┬──────────────┘
│ Results / statuses sent back to Server
▼
Server updates Web UI, sends status back to Forge
(Optional) ─────────────────────────────────────────────────────────────
▲
│ (monitors queue / load)
│
┌────────────────────────────┐
│ Woodpecker Autoscaler │
└────────────┬───────────────┘
│ dynamically spin up / down VM(s) / Agent host(s)
▼
New Agent(s) <-- connect via gRPC to Server
在 Gitea 实例注册 OAuth2 应用,获取 Client ID 和 Client Secret 备用

在 Gitea 的 Compose 模板里,加入服务片段。对于单人实例,建议使用 SQLite 数据库,看起来就像这样:
services:
gitea:
image: docker.gitea.com/gitea:1.25.2-rootless
container_name: gitea
restart: unless-stopped
user: "1000"
volumes:
- ./data:/var/lib/gitea
- ./config:/etc/gitea
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "127.0.0.1:33033:3000"
- "127.0.0.1:11211:2222"
networks:
- gitea
woodpecker-server:
image: woodpeckerci/woodpecker-server:v3
container_name: woodpecker-server
restart: unless-stopped
depends_on:
gitea:
condition: service_healthy
ports:
- '127.0.0.1:18000:8000'
networks:
- gitea
volumes:
- ./woodpecker/server/data:/var/lib/woodpecker/
environment:
- WOODPECKER_OPEN=false # disable registration
- WOODPECKER_ADMIN=${WOODPECKER_ADMIN}
- WOODPECKER_HOST=${WOODPECKER_HOST}
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
- WOODPECKER_DISABLE_VERSION_CHECK=false
- WOODPECKER_GITEA=true
- WOODPECKER_GITEA_URL=${WOODPECKER_GITEA_URL}
- WOODPECKER_GITEA_SKIP_VERIFY=false
- WOODPECKER_GITEA_CLIENT=${WOODPECKER_GITEA_CLIENT}
- WOODPECKER_GITEA_SECRET=${WOODPECKER_GITEA_SECRET}
woodpecker-agent:
image: woodpeckerci/woodpecker-agent:v3
container_name: woodpecker-agent
restart: unless-stopped
depends_on:
woodpecker-server:
condition: service_healthy
networks:
- gitea
volumes:
- ./woodpecker/agent/config:/etc/woodpecker
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WOODPECKER_SERVER=woodpecker-server:9000
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
- WOODPECKER_MAX_WORKFLOWS=2
networks:
gitea:
提前建立 Woodpecker 所需数据持久化目录
mkdir -p ./woodpecker/{server/data,agent/config}
设置用户组权限
sudo chown -R 1000:1000 ./woodpecker
sudo chmod -R 755 ./woodpecker
WOODPECKER_ADMIN 对应 Gitea 实例上允许授权的用户名
WOODPECKER_GITEA_URL 填写 Gitea 实例 URL,如 https://git.your.domain
WOODPECKER_GITEA_CLIENT 是 Gitea OAuth2 应用的 Client ID
WOODPECKER_GITEA_SECRET 是 Gitea OAuth2 应用的 Client Secret
WOODPECKER_AGENT_SECRET 随机值机密,使用 openssl rand -base64 32 生成
编辑 ./data/app.ini ,设置 Gitea 允许的 WebHook 网络主机
[webhook]
ALLOWED_HOST_LIST=external,loopback
模板和环境变量准备好后,启动服务
sudo docker compose up -d
下面是 Woodpecker 的 Nginx 反向代理配置示例,如果图方便和安全,更推荐使用 Cloudflare Tunnel 访问服务 。
server {
listen 80;
listen [::]:80;
server_name woodpecker.your.domain;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name woodpecker.your.domain;
ssl_certificate /path/to/cert/wild.via.moe.pem;
ssl_certificate_key /path/to/cert/wild.via.moe.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ecdh_curve X25519:P-256:P-384;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-CHACHA20-POLY1305:ECDHE+AES128:RSA+AES128:ECDHE+AES256:RSA+AES256';
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
access_log /var/log/nginx/woodpecker.your.domain.access.log;
error_log /var/log/nginx/woodpecker.your.domain.error.log;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
location / {
proxy_pass http://127.0.0.1:18000;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $realip_remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_redirect off;
proxy_buffering off;
chunked_transfer_encoding off;
}
}
将域名解析到服务器 IP,重载 Nginx 配置
sudo nginx -t
sudo nginx -s reload
访问 Woodpecker 域名,进入 Gitea 应用授权页面

授权成功,会进入 Woodpecker 管理台

最后,简单测试下 CI 是否正常工作。在存储库添加 CI 脚本 .woodpecker/ci-test.yaml
when:
- event: push
branch: main
steps:
- name: node-build
image: node:20-alpine
commands:
- echo "Running Node.js test..."
- node -e 'console.log("Hello, world! from Node.js")'
- echo "CI test success! Dejavu Moe says hello to you"
Woodpecker 添加这个存储库后,回到 Gitea 存储库任意提交一次。

在 Gitea 也可以看到它正常工作

一切完成!
参考资料:
Gitea/Forgejo 除了轻量还拥有强大的 CI/CD 集成支持,我暂时了解的选择项:
三者都能与 Gitea 轻松集成,最终我选择了 Woodpecker CI。
Woodpecker CI 基本服务组成为 server、agent 及可选的 autoscaler,前两者是必需的,工作流程如下:
┌──────────────────────┐
│ Code Hosting / │ <-- webhooks, events
│ Forge (e.g. Gitea) │ push / pr / commit
└─────────┬────────────┘
│ HTTP / Webhook
▼
┌──────────────────────┐
│ Woodpecker Server │
│ API / Web UI │
│ orchestration │
└─────────┬────────────┘
│ gRPC (queue / job dispatch)
┌────────────┬──────────────┼──────────────┬────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐ (more agents …)
│ Woodpecker │ │ Woodpecker │ │ Woodpecker │
│ Agent (Docker) │ │ Agent (Docker) │ … │ Agent (K8s / │
│ │ │ │ │ Docker / local)│
└────────────────┘ └────────────────┘ └────────────────┘
│ │ │
│ Executes │ Executes │ Executes
│ pipeline steps │ pipeline steps │ pipeline steps
│ (build / test / │ (build / test / │ (build / test /
│ deploy etc.) │ deploy etc.) │ deploy etc.)
▼ ▼ ▼
build logs / status build logs / status build logs / status
└──────────────┬──────────────┬──────────────┘
│ Results / statuses sent back to Server
▼
Server updates Web UI, sends status back to Forge
(Optional) ─────────────────────────────────────────────────────────────
▲
│ (monitors queue / load)
│
┌────────────────────────────┐
│ Woodpecker Autoscaler │
└────────────┬───────────────┘
│ dynamically spin up / down VM(s) / Agent host(s)
▼
New Agent(s) <-- connect via gRPC to Server
在 Gitea 实例注册 OAuth2 应用,获取 Client ID 和 Client Secret 备用

在 Gitea 的 Compose 模板里,加入服务片段。对于单人实例,建议使用 SQLite 数据库,看起来就像这样:
services:
gitea:
image: docker.gitea.com/gitea:1.25.2-rootless
container_name: gitea
restart: unless-stopped
user: "1000"
volumes:
- ./data:/var/lib/gitea
- ./config:/etc/gitea
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "127.0.0.1:33033:3000"
- "127.0.0.1:11211:2222"
networks:
- gitea
woodpecker-server:
image: woodpeckerci/woodpecker-server:v3
container_name: woodpecker-server
restart: unless-stopped
depends_on:
gitea:
condition: service_healthy
ports:
- '127.0.0.1:18000:8000'
networks:
- gitea
volumes:
- ./woodpecker/server/data:/var/lib/woodpecker/
environment:
- WOODPECKER_OPEN=false # disable registration
- WOODPECKER_ADMIN=${WOODPECKER_ADMIN}
- WOODPECKER_HOST=${WOODPECKER_HOST}
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
- WOODPECKER_DISABLE_VERSION_CHECK=false
- WOODPECKER_GITEA=true
- WOODPECKER_GITEA_URL=${WOODPECKER_GITEA_URL}
- WOODPECKER_GITEA_SKIP_VERIFY=false
- WOODPECKER_GITEA_CLIENT=${WOODPECKER_GITEA_CLIENT}
- WOODPECKER_GITEA_SECRET=${WOODPECKER_GITEA_SECRET}
woodpecker-agent:
image: woodpeckerci/woodpecker-agent:v3
container_name: woodpecker-agent
restart: unless-stopped
depends_on:
woodpecker-server:
condition: service_healthy
networks:
- gitea
volumes:
- ./woodpecker/agent/config:/etc/woodpecker
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WOODPECKER_SERVER=woodpecker-server:9000
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
- WOODPECKER_MAX_WORKFLOWS=2
networks:
gitea:
提前建立 Woodpecker 所需数据持久化目录
mkdir -p ./woodpecker/{server/data,agent/config}
设置用户组权限
sudo chown -R 1000:1000 ./woodpecker
sudo chmod -R 755 ./woodpecker
WOODPECKER_ADMIN 对应 Gitea 实例上允许授权的用户名
WOODPECKER_GITEA_URL 填写 Gitea 实例 URL,如 https://git.your.domain
WOODPECKER_GITEA_CLIENT 是 Gitea OAuth2 应用的 Client ID
WOODPECKER_GITEA_SECRET 是 Gitea OAuth2 应用的 Client Secret
WOODPECKER_AGENT_SECRET 随机值机密,使用 openssl rand -base64 32 生成
编辑 ./data/app.ini ,设置 Gitea 允许的 WebHook 网络主机
[webhook]
ALLOWED_HOST_LIST=external,loopback
模板和环境变量准备好后,启动服务
sudo docker compose up -d
下面是 Woodpecker 的 Nginx 反向代理配置示例,如果图方便和安全,更推荐使用 Cloudflare Tunnel 访问服务 。
server {
listen 80;
listen [::]:80;
server_name woodpecker.your.domain;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name woodpecker.your.domain;
ssl_certificate /path/to/cert/wild.via.moe.pem;
ssl_certificate_key /path/to/cert/wild.via.moe.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ecdh_curve X25519:P-256:P-384;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-CHACHA20-POLY1305:ECDHE+AES128:RSA+AES128:ECDHE+AES256:RSA+AES256';
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
access_log /var/log/nginx/woodpecker.your.domain.access.log;
error_log /var/log/nginx/woodpecker.your.domain.error.log;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
location / {
proxy_pass http://127.0.0.1:18000;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $realip_remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_redirect off;
proxy_buffering off;
chunked_transfer_encoding off;
}
}
将域名解析到服务器 IP,重载 Nginx 配置
sudo nginx -t
sudo nginx -s reload
访问 Woodpecker 域名,进入 Gitea 应用授权页面

授权成功,会进入 Woodpecker 管理台

最后,简单测试下 CI 是否正常工作。在存储库添加 CI 脚本 .woodpecker/ci-test.yaml
when:
- event: push
branch: main
steps:
- name: node-build
image: node:20-alpine
commands:
- echo "Running Node.js test..."
- node -e 'console.log("Hello, world! from Node.js")'
- echo "CI test success! Dejavu Moe says hello to you"
Woodpecker 添加这个存储库后,回到 Gitea 存储库任意提交一次。

在 Gitea 也可以看到它正常工作

一切完成!
参考资料: