轻量级开源 CI/CD:Woodpecker 从零部署到跑通完整流水线

软件写好了,如何部署?部署之后,又如何持续迭代、频繁发布?这是软件工程绕不开的问题。常见的答案是引入 CI/CD 工具,把测试、构建、部署交给机器自动完成。但流行的 CI/CD 工具各有侧重,能力、性能、适用场景不尽相同,该如何选择?本文以轻量、开源的 Woodpecker 为例,从部署和流水线讲起,看它如何集成测试和部署过程变得简单和优雅。
最知名的开源 CI/CD 工具非 Jenkins 莫属,功能强大、插件丰富,但对小团队或者个人 Homelab 来说偏重;GitLab CI 也很成熟,但前提是整套流程都构建在 GitLab 上;GitHub Actions 体验一流,但它不开源,构建数据都在第三方手里。
如果你的需求与我的类似——自托管、开源、轻量,并且已经在使用 Gitea 这类自建代码托管平台——Woodpecker CI 是一个值得考虑的选择。它的理念与用法和 GitHub Actions 有不少相通之处:
- 容器原生:流水线的每一步都跑在一个独立容器里,环境干净,用完即弃;
- 语法简单:一个 YAML 文件就能描述构建、测试、发布,没有太多概念负担;
- 架构简单:一个 Server 负责界面与调度,一个或多个 Agent 负责执行任务,用 Docker Compose 就能快速拉起一套;
同时,Woodpecker 完全开源、支持多种代码托管平台、不与特定厂商绑定。
本文介绍如何在 Linux 服务器上部署 Gitea 与 Woodpecker,编写第一条 Hello World 流水线,并以一个 Python 项目为例,完整演示“提交 → 测试 → PR → 构建镜像 → 自动部署”的流程。
Woodpecker 官网:https://woodpecker-ci.org/,有丰富的文档可供参考。
起源
Woodpecker 起源于开源项目 Drone。我最初使用的也是 Drone,后来才迁移到 Woodpecker。早期 Woodpecker 曾兼容 .drone.yml,如今已完全独立演进,使用自己的 .woodpecker.yaml 配置体系。
2019 年,Drone 在 0.8 版本之后将许可证从 Apache 2.0 改为私有协议,随后被 Harness 收购,核心版本转向付费用户,开源版本更新放缓。社区开发者 laszlocph 于 2019 年 4 月 3 日基于最后一个 Apache 2.0 授权的 Drone 0.8 代码库创建分叉,三天后发布第一个版本;同年 8 月 27 日,项目更名为 Woodpecker(啄木鸟)。
此后项目由社区维护至今,始终保持 Apache 2.0 协议,主仓库位于 GitHub(https://github.com/woodpecker-ci/woodpecker)。2025 年初发布 3.0 版本,目前演进到 3.18,迭代活跃。
架构一览
Woodpecker 使用 Golang 编写,轻量高效,架构也很简单,主要有两个角色:Server 和 Agent:
- Server:对外提供 Web 界面和 API(默认 8000 端口),管理用户、仓库与流水线记录,默认使用内置 SQLite 存储数据;
- Agent:真正干活的执行器,通过 gRPC(默认 9000 端口)连接 Server 领任务,为流水线的每一步在本机启动临时 Docker 容器执行。
一个流水线内部的层级是:Pipeline(一次流水线执行)→ Workflow(一个 YAML 文件)→ Step(文件里的一个步骤)。每个步骤指定一个镜像(image)和一组命令(commands)或设置(settings),步骤之间共享同一个工作区(源码在工作区里,上一步的构建产物下一步也能用),按定义顺序串行执行。
Woodpecker 的流水线使用 YAML 格式配置,整体较为清晰易读,语法参考:https://woodpecker-ci.org/docs/usage/workflow-syntax。
另外,Woodpecker 通常搭配私有的 SCM(Source Code Management,即代码托管平台)使用,支持 GitHub、Gitea、GitLab 等常用平台。Woodpecker 没有自己的用户认证系统,直接复用 SCM 提供的用户认证。
一个典型的任务流程是:
- 事先,将 Woodpecker 接入 SCM,并将仓库激活到 Woodpecker 中,仓库中包含
.woodpecker目录和相应的流水线文件; - 用户推送代码到 SCM 时,触发 SCM 的 Webhook,调用 Woodpecker Server 开始运行;
- Woodpecker Server 调度任务给 Woodpecker Agent,Agent 拉取代码,依次执行 workflow 和 step 里定义的步骤。
部署
本文基于一下版本测试
- Woodpecker 3.18.0
- Gitea 为 1.27.x
我们的目标拓扑是:一台服务器上,用一份 docker-compose.yml 同时运行 Gitea、Woodpecker Server、Woodpecker Agent,三者处于同一个 Docker 网络。
准备工作
确认 Docker 环境:
$ docker version # 29.8.0
$ docker compose version # v5.5.1
编写 compose 文件
由于 Gitea、Woodpecker Server、Agent 都部署在同一台机器上,为了让服务间通信和浏览器访问都简单可靠,本文统一用机器的真实 IP 地址加端口的方式访问,示例中使用 192.168.1.10 这个本机 IP。
先创建 .env 文件,把所有敏感配置集中放在这里(.env 不要提交到任何仓库):
# .env
# Woodpecker 对外访问地址(注意:不要以 / 结尾)
WOODPECKER_HOST=http://192.168.1.10:8000
# Gitea OAuth 应用的凭证(创建 OAuth 应用后回填)
WOODPECKER_GITEA_CLIENT=待回填
WOODPECKER_GITEA_SECRET=待回填
# Server 与 Agent 之间的通信密钥,随机生成即可
# 生成命令:openssl rand -hex 32
WOODPECKER_AGENT_SECRET=换成你的随机字符串
然后编写 docker-compose.yml:
services:
gitea:
image: gitea/gitea:1.27
restart: always
ports:
- "3000:3000"
volumes:
- ./gitea-data:/data
environment:
# 对外访问地址,影响页面跳转、clone 地址和 webhook 回调
GITEA__server__ROOT_URL: http://192.168.1.10:3000/
GITEA__webhook__ALLOWED_HOST_LIST: external,loopback,private
woodpecker-server:
image: woodpeckerci/woodpecker-server:v3
restart: always
depends_on:
- gitea
ports:
- "8000:8000"
volumes:
- ./woodpecker-server-data:/var/lib/woodpecker
environment:
- WOODPECKER_OPEN=true # 允许 Gitea 用户注册登录
- WOODPECKER_ADMIN=gitadmin # 该 Gitea 用户将成为 Woodpecker 管理员
- WOODPECKER_HOST=${WOODPECKER_HOST}
- WOODPECKER_GITEA=true
- WOODPECKER_GITEA_URL=http://192.168.1.10:3000
- WOODPECKER_GITEA_CLIENT=${WOODPECKER_GITEA_CLIENT}
- WOODPECKER_GITEA_SECRET=${WOODPECKER_GITEA_SECRET}
# ---- Agent 通信密钥 ----
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
woodpecker-agent:
image: woodpeckerci/woodpecker-agent:v3
restart: always
depends_on:
- woodpecker-server
command: agent
volumes:
- ./woodpecker-agent-config:/etc/woodpecker
- /var/run/docker.sock:/var/run/docker.sock # 用宿主机 Docker 跑步骤容器
environment:
- WOODPECKER_SERVER=woodpecker-server:9000
- WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}
- WOODPECKER_MAX_WORKFLOWS=5 # 本 Agent 能同时运行的最大 workflow 数量
几个关键点解释一下:
WOODPECKER_HOST:Server 的对外地址,浏览器访问、OAuth 回跳、Agent 连接(跨机时)都依赖它,必须与浏览器实际访问的地址完全一致;WOODPECKER_GITEA_URL:Gitea 的地址,Server 用它调用 Gitea API、发起 OAuth 跳转;WOODPECKER_AGENT_SECRET:Server 和 Agent 之间的“暗号”(共享密钥),两边必须一致;WOODPECKER_GITEA_CLIENT/WOODPECKER_GITEA_SECRET:在 Gitea 中创建的 OAuth 凭据,Woodpecker 用它完成认证;WOODPECKER_MAX_WORKFLOWS:设置 Agent 能同时运行的最大 workflow 数量。
Gitea 和 Woodpecker 都支持 MySQL 和 PostgreSQL,这里简化起见不指定数据库类型,默认使用 SQLite。生产环境建议换用具备高可用能力的数据库服务,并做好备份。
启动 Gitea 并创建 OAuth 应用
Gitea 的 OAuth 凭证需要先创建,所以我们分两步启动:
$ docker compose up -d gitea
第一步:初始化 Gitea。 浏览器打开 http://192.168.1.10:3000,进入 Gitea 的安装页面:
- 数据库保持默认的 SQLite3(演示环境够用,生产环境可更换为 MySQL);
- 其余保持默认,滚动到页面下方“管理员账号设置”,创建管理员:用户名
gitadmin,设置一个强密码; - 点击“立即安装”。
第二步:创建 OAuth 应用。 用 gitadmin 登录 Gitea,右上角头像 → 设置(Settings)→ 应用(Applications)→ 管理 OAuth2 应用 → 创建新的 OAuth2 应用程序:
- 应用名称:
Woodpecker - 重定向 URI(Redirect URI):
http://192.168.1.10:8000/authorize
注意:回调地址必须与
WOODPECKER_HOST + /authorize完全一致(协议、端口、IP 任一项不同都会导致失败),这是登录失败的常见原因。
点击“创建应用”后,页面会显示 Client ID 和 Client Secret(Secret 仅显示一次,请注意保存),回填到 .env:
WOODPECKER_GITEA_CLIENT=刚拿到的 client_id
WOODPECKER_GITEA_SECRET=刚拿到的 client_secret
如果你同时是 Gitea 和 Woodpecker 的管理员,也可以在 Gitea 的右上角头像进入 “站点管理 → 设置 → 应用”,创建系统级 OAuth 应用,对所有用户可见,更适合多人共用的场景。
第三步:启动全部服务。
$ mkdir woodpecker-server-data
$ sudo chown -R 1000:1000 woodpecker-server-data # Server 容器以 UID 1000 运行,不改属主 SQLite 可能写不进去
$ docker compose up -d
$ docker compose ps # 确保 3 个服务都正常
登录验证
浏览器打开 http://192.168.1.10:8000 进入 Woodpecker 页面,点击 Login 跳转到 Gitea 的授权页面,授权后回到 Woodpecker 首页。
此时 Gitea 和 Woodpecker 的界面都是全新的,因为还没有项目和代码仓库。
编写流水线
激活仓库
- 在 Gitea 上创建一个仓库
gitadmin/demo-api(建议勾选“初始化仓库”); - 到 Woodpecker 界面右侧点 + (Add Repository) → Enable 激活。
激活时 Woodpecker 会向 Gitea 的仓库写入一个 webhook(因此需要对仓库具有管理员权限),此后 Gitea 上的 push、PR、tag 等事件都会通知 Woodpecker。
Hello World
Woodpecker 的流水线配置放在仓库里。单文件时用根目录的 .woodpecker.yaml;也可以放在 .woodpecker/ 目录下,一个 YAML 文件就是一个独立 workflow(文件名即 workflow 名)。
我们从最简单的单文件开始,在仓库根目录创建 .woodpecker.yaml:
when:
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: greeting
image: alpine
commands:
- echo "hello, woodpecker" > greeting.txt
- name: shout
image: python:3.14-slim
commands:
- python -c "print(open('greeting.txt').read().strip().upper())"
这个例子虽然小,却用上了 Woodpecker 最重要的两个特性:
- 每个步骤是一个独立的容器——
greeting用的是 alpine 镜像,shout换成了 python 镜像,互不干扰; - 步骤之间共享同一个工作区——
greeting写下的greeting.txt,shout能直接读到,最终输出HELLO, WOODPECKER。
提交并推送到默认分支:
$ git add .woodpecker.yaml
$ git commit -m "ci: first pipeline"
$ git push
回到 Woodpecker 界面,几秒内就会出现一条新的流水线记录,点进去能看到 clone、greeting、shout 三个步骤:clone 是 Woodpecker 自动加上的(把仓库拉进工作区),后两个是你在 YAML 里定义的。每个步骤都能展开看实时日志,全部变绿即成功。
这就是 Woodpecker 的基本心智模型:每个步骤是一个容器,共享一个工作区,顺序执行,非零退出码即失败。
常用语法速览
触发条件 when。流水线级(文件顶部)和步骤级(步骤内)都可以写,支持事件、分支、路径等过滤:
when:
- event: push
branch: main # 只在 main 分支的 push 触发
- event: pull_request # PR 也触发
- event: tag # 打 tag 也触发
常用事件一览:push(推送)、pull_request(PR 打开或更新)、tag(打标签)、manual(界面手动触发)、cron(定时任务)、deployment(部署事件)、release(发布 Release)。
路径过滤也常用——文档变了不跑构建:
when:
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
path: "src/**/*" # 只有 src 下的文件变化才触发
内置环境变量。Woodpecker 会往每个步骤注入一批 CI_ 开头的变量,常用的有:
CI_REPO:仓库全名,如gitadmin/demo-api;CI_REPO_DEFAULT_BRANCH:默认分支名(通常是main);CI_COMMIT_SHA/CI_COMMIT_BRANCH:提交哈希 / 分支名;CI_COMMIT_TAG:标签名(tag 事件时才有值);CI_COMMIT_MESSAGE:提交信息;CI_PIPELINE_NUMBER:流水线序号,同一仓库内从 1 递增,常用来给镜像打版本;CI_PIPELINE_PARENT:父流水线序号(作为子流水线触发时才有值);CI_PIPELINE_URL:本次流水线在 Woodpecker 界面的链接;CI_PIPELINE_EVENT:本次触发事件类型。
它们既是步骤容器里的环境变量,也能在配置文件中用 ${...} 直接替换,甚至支持 Bash 风格的截取,比如 ${CI_COMMIT_SHA:0:8} 取短哈希。
不过要分清“两种取值时机”:
steps:
- name: info
image: alpine
commands:
- echo "repo: ${CI_REPO}" # 配置阶段替换:加载 YAML 时就替换成字面量
- echo "repo: $CI_REPO" # 运行时读取:步骤容器里再展开(推荐)
- echo "path: $${PATH}" # $$ 转义:${...} 原样传给运行时 shell
${VAR}:解析配置时被 Woodpecker 预先替换成具体值,等于写死进脚本;$VAR:原样保留,由步骤容器里的 shell 在运行时展开(推荐);$${VAR}:$$转义后${...}原样传给运行时——引用PATH或environment注入的自定义变量(它们不参与配置阶段替换)时需要。
自定义环境变量用 environment:
steps:
- name: test
image: python:3.14-slim
environment:
PIP_INDEX_URL: https://mirrors.aliyun.com/pypi/simple
commands:
- pip install pytest
允许失败的步骤。比如 lint 想提示但不卡流程:
steps:
- name: lint
image: python:3.14-slim
commands:
- pip install ruff && ruff check .
failure: ignore # 失败标记为红,但不影响整体结果
Secrets:管理敏感信息
镜像仓库密码、部署私钥等敏感信息不应写入 YAML。在 Woodpecker 界面进入仓库 → Settings → Secrets 添加:
registry_user:镜像仓库用户名registry_password:镜像仓库密码/Token
在流水线里通过 from_secret 引用(环境和插件参数两种用法都支持):
steps:
- name: deploy
image: some/deploy-tool
environment:
TOKEN_ENV:
from_secret: registry_password # 注入为环境变量 TOKEN_ENV
settings:
api_token:
from_secret: registry_password # 传给插件的 settings 参数
两个值得注意的默认行为:
- Secrets 默认不会注入 pull_request 事件,防止密钥通过外部 PR 泄露。确有需要时,可以在 Secret 设置里手动勾选允许的事件;
- 可以给 Secret 限定“只允许哪些插件镜像使用”,进一步收窄暴露面。
Secret 也分系统全局和项目级别两种,如果多个仓库都要用同一组凭据,可以由管理员在系统级统一添加。
实战:Python 项目从测试到自动部署
现在来看一个更完整的项目:push 到默认分支自动跑测试、构建镜像并推送、SSH 部署到测试服务器。
示例是一个用 uv 管理的 Python Web 小项目,最终以 docker compose 的方式跑在测试机上。
准备项目
使用 uv 创建并管理新项目:
$ mkdir web-app
$ cd web-app
$ uv init --name=web-app .
$ uv add flask
$ uv add --group test pytest ruff mypy # pytest 等测试/检查工具放进 test 依赖组
$ uv add --group wsgi gunicorn # 生产服务器放进 wsgi 依赖组
修改 main.py:
from flask import Flask, jsonify
app = Flask(__name__)
@app.get("/")
def hello():
return jsonify({"message": "Hello, World!"})
@app.get("/health")
def health():
return jsonify({"status": "ok"})
if __name__ == "__main__":
app.run(debug=True)
再添加单元测试 tests/test_app.py:
import pytest
from main import app
@pytest.fixture()
def client():
app.config["TESTING"] = True
with app.test_client() as client:
yield client
def test_hello_returns_greeting(client):
resp = client.get("/")
assert resp.status_code == 200
assert resp.get_json() == {"message": "Hello, World!"}
def test_health_returns_ok(client):
resp = client.get("/health")
assert resp.status_code == 200
assert resp.get_json() == {"status": "ok"}
def test_unknown_path_returns_404(client):
resp = client.get("/no-such-page")
assert resp.status_code == 404
Dockerfile 按常规写法把项目构建成可运行镜像即可;compose.yaml 是部署模板,注意里面要有一行 image:,部署脚本靠替换这一行来指定版本:
FROM python:3.14-slim
RUN pip install uv -i https://mirrors.aliyun.com/pypi/simple
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --group wsgi
COPY main.py ./
ENV PATH="/app/.venv/bin:$PATH"
EXPOSE 8080
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "main:app"]
compose.yaml(部署模板):
services:
web:
image: 192.168.1.10:5000/mybuild/web-app # 占位用,部署时会被替换成具体版本
ports:
- "8080:8080"
这里复用了上一篇文章 Docker buildx 实现跨架构构建与多架构镜像里的镜像仓库。
代码和逻辑都很简单,有兴趣的可以照着做一遍。最终的项目结构如下:
.
├── .woodpecker
│ └── ci.yaml
├── compose.yaml
├── Dockerfile
├── main.py
├── pyproject.toml
├── README.md
├── tests
│ └── test_app.py
└── uv.lock
流水线配置
在仓库根目录创建 .woodpecker/ci.yaml(目录形式,一个文件就是一个 workflow):
when:
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH} # 默认分支,通常是 main
- event: pull_request # PR 也触发,但只跑测试(见下方步骤级 when)
steps:
- name: test
image: astral/uv:python3.14-alpine
environment:
UV_CACHE_DIR: /cache/uv
UV_LINK_MODE: copy
PIP_CACHE_DIR: /cache/pip
MYPY_CACHE_DIR: /cache/mypy
RUFF_CACHE_DIR: /cache/ruff
PYTHONPATH: . # 让 pytest 能导入根目录的 main.py
commands:
- uv sync --group test # 安装项目依赖(flask)+ test 组
- uv run pytest .
- uv run mypy .
- uv run ruff check .
volumes:
- /data/program/woodpecker/agent-cache:/cache # 宿主机缓存目录,按需修改
- name: build-image
when:
- event: push # PR 时跳过,只测不发布
image: woodpeckerci/plugin-kaniko
settings:
cache: true # 开启构建缓存,缓存仓库从 destination 自动推断,可用 cache_repo 显式指定
repo: mybuild/web-app
registry: 192.168.1.10:5000
insecure: true # registry 为 HTTP 时需要;若已配置 TLS 则删除此行
tags: ${CI_PIPELINE_NUMBER}-${CI_COMMIT_SHA:0:8}
username: admin
password:
from_secret: registry_password
dockerfile: ./Dockerfile
- name: copy-compose
when:
- event: push
image: appleboy/drone-scp
settings:
host: 127.0.0.1 # 测试机与 CI 同机时用回环地址即可
user: root
key:
from_secret: deploy_key
source:
- compose.yaml
target: /tmp/
command_timeout: 1m
- name: deploy
when:
- event: push
image: appleboy/drone-ssh
settings:
host: 127.0.0.1
username: root
key:
from_secret: deploy_key
command_timeout: 5m
script:
- mkdir -p /opt/web-app
- export IMAGE=192.168.1.10:5000/mybuild/web-app:${CI_PIPELINE_NUMBER}-${CI_COMMIT_SHA:0:8}
- bash -c "cd /opt/web-app && [[ -f compose.yaml ]] && cp compose.yaml compose.yaml.last_version"
- 'sed "s#image: .*#image: $IMAGE#" /tmp/compose.yaml > /opt/web-app/compose.yaml'
- cd /opt/web-app && docker compose up -d
必要的设置:
- 流水线中用到了
registry_password(镜像仓库密码)和deploy_key(测试机 SSH 私钥)两个 Secret,需要先把它们加到仓库设置中。仓库用户名、主机地址这类不敏感信息直接写在流水线里即可,口令和私钥才需要走 Secret; - 在流水线里挂载宿主机目录属于提权能力,需要在 Woodpecker 的仓库设置中开启 Trusted(Project settings → Trusted → Volumes),否则挂载会失败。
常见的需要启用的配置项还有
Project → Allow Deployments、Trusted → Security等,可根据报错信息和日志按需启用。
几点说明:
- 两级触发条件的配合:文件顶部的
when让默认分支 push 和 PR 都触发流水线;后面三个步骤各自带when: event: push,PR 时自动跳过——这是步骤级过滤的典型用法:测试对所有变更生效,发布只对合入主干生效; - 测试步骤挂了宿主机缓存卷,让 uv/pip/mypy/ruff 的缓存在流水线之间复用,能明显提速;
- 镜像标签用
${CI_PIPELINE_NUMBER}-${CI_COMMIT_SHA:0:8},每次提交都唯一,而且能追溯到流水线序号与提交哈希。想改成打 tag 才发布正式版,把后三个步骤的when换成event: tag,并给构建插件开启auto_tag,按语义化版本自动生成镜像标签; - 流水线里用到了 plugin-kaniko、drone-scp、drone-ssh 三个插件,更多插件可以到官网查看:https://woodpecker-ci.org/plugins。
流水线的逻辑:
- 先执行单元测试、lint 和类型检查;
- 然后构建 Docker 镜像并推送到自建的 Docker Registry(HTTP 协议:kaniko 侧用
insecure: true推送,拉取侧需在测试机的 daemon.json 配置insecure-registries,参见上一篇文章); - drone-scp 把 compose 模板分发到测试机;
- drone-ssh 远程执行——备份旧 compose、用 sed 把
image:行替换成本次构建的版本,最后docker compose up -d滚动更新。
缓存。 CI 的每一步都跑在一次性容器里,不做缓存的话,每次流水线都要从零下载全部依赖、重建全部镜像层,跑测试的时间大半耗在等待下载上。缓存能成倍提升构建速度、显著减少等待时间——本文的流水线里其实埋了两层:test 步骤的宿主机缓存卷(依赖缓存)和 build-image 步骤的
cache: true(镜像层缓存)。当流水线中某些步骤耗时较长时,值得优先考虑能否通过缓存优化。
验证
- 在 Gitea 上新建空仓库(如
gitadmin/web-app),并在 Woodpecker 里激活(步骤同“激活仓库”一节); - 根据流水线的需求,配置
registry_password和deploy_key两个 Secret,并开启Trusted → Volumes设置; - 推送代码:
$ git add . && git commit -m "feat: demo app with ci" && git push
预期:test 步骤依次输出 pytest、mypy、ruff 的结果;build-image 日志里能看到 kaniko 的构建与推送信息;copy-compose、deploy 相继变绿。到测试机上执行 cd /opt/web-app && docker compose ps,能看到 web 服务已经用新镜像跑起来了;浏览器访问 http://<测试机IP>:8080/health,返回 {"status": "ok"} 即部署成功。
再开一个 PR 试试:流水线只会执行 test 步骤,后面三个步骤自动跳过,合入默认分支后才会构建镜像并部署——这正是步骤级 when 的用途。
woodpecker-cli:命令行操作与本地执行
前面的操作都在 Web 界面完成,但改流水线时最麻烦的是“推一次、等一次”。官方的 woodpecker-cli 能把很多操作搬回终端,其中 exec 甚至可以在本地直接跑流水线。
安装:到 GitHub Releases(https://github.com/woodpecker-ci/woodpecker/releases)下载对应平台的压缩包,解压出的 woodpecker-cli 放进 PATH 即可;部分 Linux 发行版的软件源里也有打包。
连接服务器:
$ woodpecker-cli setup --server http://192.168.1.10:8000 --token <你的令牌>
woodpecker token 获取:从 Woodpecker 右上角的头像进入用户设置,在
Cli & App中可以获得Personal Access Token。
令牌在 Woodpecker 界面的用户设置(User settings)里生成。连接信息保存在本地,还支持用 context 管理多套环境。
常用命令速览:
woodpecker-cli info:查看当前连接的用户与服务器信息;woodpecker-cli repo ls:列出已激活的仓库;woodpecker-cli pipeline ls:列出最近的流水线;woodpecker-cli pipeline log show:在终端查看某次流水线的日志;woodpecker-cli lint .woodpecker.yaml:校验配置文件语法,加--strict更严格。
本地试跑流水线——这是 CLI 最实用的能力。在仓库目录里执行:
$ woodpecker-cli exec
它会读取本地 .woodpecker.yaml(或 .woodpecker/ 目录),用本机的 Docker 后端真实执行各步骤,不推代码就能验证语法和命令输出。默认执行全部 workflow,用 --workflow-name 可以只跑其中一个;涉及 Secret 的步骤,可以用 --secrets 临时传入测试值。
总结
回顾整套体系:一台服务器、一份 compose 文件,Gitea 负责代码托管,Woodpecker 负责构建,SQLite 存储记录,几十行 YAML 即可描述从测试到发布的全部流程。对个人项目和小团队来说,这就是一套“麻雀虽小,五脏俱全”的私有 CI/CD。
在本文之外,还有几个日常会遇到的场景:
- deployment 部署事件:除 push/PR 外,Woodpecker 还支持
deployment事件,可通过界面或 API 发起,配合when: event: deployment单独编写发布流水线,实现“测试通过后再手动部署到指定环境”的流程; - 多 Agent 协作:一个 Server 可以接入多个 Agent,用
WOODPECKER_MAX_WORKFLOWS控制单个 Agent 的并发数;还可以给 Agent 配置标签(labels),把特定任务(如 arm64 构建)调度到对应的机器执行; - matrix 构建矩阵:
matrix关键字一次定义多组变量,生成批量任务——比如同时在多个 Python 版本、amd64/arm64 多平台上跑测试。
CI/CD 是 DevOps 的重要一环,而 Woodpecker 是补齐这块拼图的极佳工具,面临相似场景的时候,非常推荐 Woodpecker。
Last updates at Sep 13,2026
Views (8)
total 0 comments