自建 CI/CD 自动化流水线实战:Gitea + Drone 实现代码推送即部署
代码写好了,本地测试通过,然后怎么部署到服务…
代码写好了,本地测试通过,然后怎么部署到服务器?手动 SSH 登录、git pull、重启服务?一次两次还行,项目多了你就知道这活儿有多烦人。
今天这篇就是来解决这个问题的。咱们用 Gitea + Drone CI 走一套完整的自动化流水线:Gitea 做自建 Git 仓库,Drone 做 CI/CD 引擎,推到仓库自动触发构建、自动部署到服务器。全程容器化,不污染宿主机,配置一次以后只管 git push。
为什么选 Gitea + Drone?
市面上自建 CI/CD 的选择不少,简单列个对比:
- GitLab CE:功能最全,但太重了。单是 Runner 的安装和管理就够喝一壶,内存起步 4GB,资源不够的机器别碰。
- Jenkins:生态最大,但配置繁琐,插件依赖严重,界面风格还停留在 2015 年。
- Woodpecker CI:轻量,但社区规模相对小,模板生态不够丰富。
- Gitea + Drone:两个容器加起来不到 200MB 镜像大小,内存占用低,配置简单,够用。这是自部署场景下性价比最高的组合。
Gitea 不用多介绍,Go 写的高性能 Git 服务,一个二进制搞定,界面和 GitHub 几乎一致。Drone 也是 Go 写的 CI 引擎,用 YAML 定义流水线,和 Gitea 是「天作之合」——OAuth 直连,推送事件自动触发。
先上架构图(脑补版)
整个链路大概长这样:
你本地开发 → git push → Gitea 收到推送 → Webhook 通知 Drone → Drone 启动 Runner 容器 → 拉代码 → 执行构建/测试 → 编译产物/构建镜像 → 部署到目标服务器
整个过程全自动,你唯一要做的就是 git push。
第一步:用 Docker Compose 部署 Gitea + Drone
我习惯用 Docker Compose 统一管理,目录结构清晰,迁移也方便。先创建一个项目目录:
mkdir -p ~/ci-stack && cd ~/ci-stack
mkdir -p data/gitea data/drone data/drone-runner
然后写 compose.yml:
cat <<'EOF' > compose.yml
version: "3.8"
networks:
ci-net:
driver: bridge
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
restart: unless-stopped
networks:
- ci-net
ports:
- "3000:3000"
- "2222:22" # Gitea SSH 端口,避免和主机 SSH 冲突
volumes:
- ./data/gitea:/data
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=sqlite3
- GITEA__server__DOMAIN=git.你的域名.com
- GITEA__server__SSH_DOMAIN=git.你的域名.com
drone-server:
image: drone/drone:latest
container_name: drone-server
restart: unless-stopped
networks:
- ci-net
ports:
- "3080:80"
volumes:
- ./data/drone:/data
environment:
- DRONE_GITEA_SERVER=http://gitea:3000
- DRONE_GITEA_CLIENT_ID= # 稍后在 Gitea 创建
- DRONE_GITEA_CLIENT_SECRET= # 稍后在 Gitea 创建
- DRONE_RPC_SECRET=changeme-long-random-secret
- DRONE_SERVER_HOST=ci.你的域名.com
- DRONE_SERVER_PROTO=https
- DRONE_USER_CREATE=username:你的Gitea用户名,admin:true
drone-runner:
image: drone/drone-runner-docker:latest
container_name: drone-runner
restart: unless-stopped
networks:
- ci-net
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data/drone-runner:/data
environment:
- DRONE_RPC_PROTO=https
- DRONE_RPC_HOST=drone-server
- DRONE_RPC_SECRET=changeme-long-random-secret
- DRONE_RUNNER_CAPACITY=2
- DRONE_RUNNER_NAME=runner-1
EOF
几个关键点提醒:
DRONE_RPC_SECRET必须是一个强随机字符串,server 和 runner 两边要一致DRONE_RUNNER_CAPACITY=2表示同时最多跑 2 个 Pipeline,根据你服务器资源调整- Runner 需要挂载宿主机的 Docker socket,因为它要动态创建容器来执行流水线
先别急着启动。我们需要先到 Gitea 里创建 OAuth 应用,拿到 client_id 和 client_secret 才能让 Drone 和 Gitea 握手。
第二步:启动 Gitea 并配置 OAuth
先启动 Gitea:
docker compose up -d gitea
浏览器打开 http://你的服务器IP:3000,首次访问会进入安装页面。按提示设置管理员账号(记下来!),数据库选 SQLite3 就够了,个人或小团队场景完全够用,没必要为了 MySQL 多开一个容器。
安装完成后,用管理员账号登录,去右上角头像 → 设置 → 应用 → 创建新 OAuth2 应用:
- 应用名称:Drone CI
- 重定向 URI:
https://ci.你的域名.com/login(注意这是 Drone 的回调地址,要和实际域名一致)
创建后会得到 Client ID 和 Client Secret。把它俩填回到之前的 compose.yml 里,然后启动整套服务:
docker compose up -d
第三步:配置反向代理和自动 HTTPS
我强烈建议给 Gitea 和 Drone 配上域名和 HTTPS。用 Caddy 最省事,一行配置搞定自动证书:
git.你的域名.com {
reverse_proxy gitea:3000
}
ci.你的域名.com {
reverse_proxy drone-server:80
}
如果你用 Nginx,自己配 certbot 或 acme.sh 也一样,就是多几行。HTTPS 是刚需——Drone 的 OAuth 回调必须走 HTTPS,浏览器也认这个。
第四步:写一个实际的 Pipeline
现在整套 CI 环境已经跑起来了。登录 Drone 界面(https://ci.你的域名.com),用 Gitea 账号点一下「Authorize」,就能看到你的 Gitea 仓库列表了。激活一个仓库,Drone 就开始监听它的推送事件。
接下来,在你的项目根目录创建 .drone.yml,这就是流水线的定义文件。下面我给一个实际项目用的例子——一个 Node.js 后端服务的完整流水线:
---
kind: pipeline
type: docker
name: build-and-deploy
trigger:
branch:
- main
event:
- push
steps:
- name: install-deps
image: node:20-alpine
commands:
- npm ci --only=production
- cp -r node_modules /tmp/node_modules
- name: build
image: node:20-alpine
commands:
- cp -r /tmp/node_modules ./node_modules
- npm run build
- cp -r dist /tmp/artifacts
- name: docker-build
image: plugins/docker
settings:
repo: registry.你的域名.com/myapp
tags: latest
dockerfile: Dockerfile
registry: registry.你的域名.com
username:
from_secret: docker_username
password:
from_secret: docker_password
when:
branch: main
- name: deploy
image: alpine:latest
commands:
- apk add --no-cache openssh-client
- mkdir -p ~/.ssh
- echo "$SSH_KEY" > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- ssh -o StrictHostKeyChecking=no user@目标服务器 "
docker pull registry.你的域名.com/myapp:latest &&
docker compose up -d myapp
"
environment:
SSH_KEY:
from_secret: ssh_private_key
when:
branch: main
这个流水线做了四件事:
- install-deps:拉依赖,注意我用
npm ci而不是npm install,因为它更严格、更快,而且保证 lockfile 一致。 - build:构建产物,用 copy 的方式跨步骤传递数据,这是 Drone 的标准做法。
- docker-build:用官方 Docker 插件构建镜像并推送到私有 registry。
- deploy:SSH 到目标服务器,拉取新镜像并重启容器。
用到了几个要点:
敏感信息(Docker 仓库密码、SSH 私钥)通过 from_secret 注入,而不是直接写在 YAML 里。这些 secret 需要在 Drone 仓库设置里预先配置好——仓库 → Settings → Secrets,逐个添加。记住 secret 的名字要和 YAML 里的对应。
第五步:秘密管理与权限控制
Drone 的秘密管理有一个值得注意的点:secret 只在信任的仓库里才自动注入到所有步骤。如果仓库是「非信任」状态,secret 只会在 when 条件匹配的步骤里注入。这对安全来说是好事,但也意味着如果你的流水线里 secret 一直为空,先去检查仓库的 Trusted 开关。
另外还有几个安全建议:
- SSH 密钥单独创建一个 部署专用用户,权限仅限于目标服务的目录和 Docker 操作,不要用 root
- Runner 容器本身就挂载了 Docker socket,不要开放 Runner 到公网,只能内网访问
- 定期轮换 Drone RPC secret 和 Gitea OAuth 密钥
第六步:调试与常见问题
流水线跑不起来?别慌,先看 Drone Runner 的日志:
docker logs drone-runner -f --tail 50
常见翻车场景:
- Runner 连不上 Server:检查 DRONE_RPC_HOST 和 DRONE_RPC_SECRET 是不是一致,检查 Docker 网络
- Webhook 没触发:到 Gitea 仓库设置 → Webhooks 里检查是否收到推送事件,注意 Drone 2.0 之后不再需要手动配置 Webhook,激活仓库时自动创建
- Pipeline 一直 pending:一般是 Runner 的资源不够,
DRONE_RUNNER_CAPACITY调大或者检查宿主机磁盘/Docker 状态 - Docker 构建失败:Runner 默认用宿主机的 Docker 构建,如果你的 Docker 版本太老可能不兼容,建议 Docker 版本不低于 24.x
总结
这一套 Gitea + Drone 组合,是我目前在用的 CI/CD 方案。从去年搭好到现在,三个项目跑了快一年,期间只因为升级 Drone 版本重启过一次。稳定、轻量、够用。
当然它也有短板——Pipeline 的 YAML 语法不如 GitHub Actions 丰富,日志输出没有 GitLab CI 那么漂亮,社区插件数量也有限。但它最大的优势是:你自己的服务器,你自己的代码,你自己说了算。没有 Action 分钟限制,没有 Runner 并发数限制,也不怕平台方哪天改政策。
最后给还在犹豫的朋友一句话:自动化流水线这个坑,越早入越值。刚开始配的时候会花一两个小时,但只要跑起来,后面省的是你每个迭代都重复的 SSH + docker compose up 那几十次操作。时间成本算一算,回本周期不到两周。
祝你的代码走上自动化流水线,从此告别手动部署。
