手把手教程:用 Docker Compose 搭建私有 CI/CD 平台(Gitea + Act Runner)
前言
在云原生时代,GitHub Actions 几乎成为了 CI/CD 的标准。但对于个人开发者、私有项目或者内网开发环境来说,将数据托管在 GitHub 并不总是最佳选择。
你是否想过拥有一套完全私有的、类似 GitHub Actions 体验的 CI/CD 系统?答案是肯定的。Gitea 搭配 Act Runner,可以让你在自己的服务器(甚至是 NAS)上轻松实现这一目标。
本文将基于我个人的实战配置,带你一步步搭建这套系统,并实现一个自动登录宿主机执行部署脚本的 Workflow。
架构概览
在开始之前,我们先了解一下我们的部署架构:
- Gitea:轻量级的自托管 Git 服务(类似私有版 GitHub)。
- Act Runner:Gitea 官方的 Actions 运行器,它负责监听 Gitea 的事件并执行业务逻辑。
- Docker-in-Docker 模式:Runner 本身是一个容器,它通过挂载宿主机的
/var/run/docker.sock,在接收到任务后,动态创建新的"作业容器(Job Container)"来运行 Workflow。这保证了每次构建环境的纯净和隔离。
第一步:编写 Docker Compose 配置
我们将使用 docker-compose.yml 来统一管理 Gitea 和 Runner。
以下是完整的配置文件,我将重点解释其中的关键参数:
version: "3"
networks:
nas_network:
driver: bridge
services:
gitea:
image: gitea/gitea:latest
container_name: nas_gitea
environment:
- USER_UID=1000
- USER_GID=1000
# 数据库配置(这里使用外部已存在的 MySQL)
- DB_TYPE=mysql
- DB_HOST=db
- DB_NAME=gitea
- DB_USER=gitea
- DB_PASSWD=your_strong_password_here
# 🚀 关键:配置代理
# 如果你的服务器在国内,拉取 Docker 镜像或访问外网较慢,这一步至关重要
- HTTP_PROXY=http://your_proxy_ip:port
- HTTPS_PROXY=http://your_proxy_ip:port
- NO_PROXY=localhost,127.0.0.1,.cn,.com.cn,.中国,gitee.com,giteeusercontent.com
restart: always
volumes:
- ./gitea/data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "13000:3000" # Web UI
- "10022:22" # SSH
networks:
- nas_network
gitrunner:
image: gitea/act_runner:nightly
container_name: nas_gitea_runner
restart: always
# 🚀 关键:让 Runner 能通过 host-gateway 访问宿主机
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
# 🚀 关键:挂载 Docker Socket(实现 DinD)
- /var/run/docker.sock:/var/run/docker.sock
- ./gitrunner/data:/data
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA_INSTANCE_URL=http://gitea:3000
- GITEA_RUNNER_REGISTRATION_TOKEN=your_runner_registration_token_here
- GITEA_RUNNER_NAME=gitea-runner
# 🚀 关键:定义 Runner 支持的标签及对应的镜像
- GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:16-bullseye,ubuntu-22.04:docker://node:16-bullseye,self-hosted
networks:
- nas_network
配置详解
1. 代理设置 (HTTP_PROXY)
在国内环境,act_runner 在运行过程中需要下载 node 镜像或其他 Actions 插件。如果不配置代理,很容易导致超时失败。请将 your_proxy_ip:port 替换为你局域网内的代理地址。
2. Docker Socket 挂载
- /var/run/docker.sock:/var/run/docker.sock 是灵魂所在。它允许 Runner 容器控制宿主机的 Docker,从而在宿主机上创建 Job 容器。
3. extra_hosts
host.docker.internal:host-gateway 这一行非常有用。它使得 Runner 内部可以通过 host.docker.internal 这个域名访问到宿主机。虽然我们在下面的 Workflow 中用了 IP,但在某些复杂网络环境下,这个 Host 映射是救命稻草。
4. Runner Labels
ubuntu-latest:docker://node:16-bullseye 意味着:当 Workflow 中指定 runs-on: ubuntu-latest 时,Runner 会拉取 node:16-bullseye 镜像作为执行环境。
第二步:启动服务
将上面的 docker-compose.yml 保存后,在同级目录下执行:
检查状态:
你应该能看到 nas_gitea 和 nas_gitea_runner 都在运行中。
此时访问 http://<你的服务器IP>:13000,完成 Gitea 的初始化安装。进入管理后台,你应该能看到 Runner 已经在线。
第三步:编写 Workflow(自动化部署)
现在,我们在 Gitea 仓库中创建 .gitea/workflows/deploy.yml。
我们的目标是:当代码推送到 master 分支时,自动登录到宿主机,拉取最新代码并重启应用。
name: Deploy
on:
push:
branches: [master]
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Setup SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
printf '%s\n' "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
# ✅ 关键:添加宿主机指纹到 known_hosts
# Docker 默认 bridge 网络中宿主机的网关地址
ssh-keyscan -H host.docker.internal >> ~/.ssh/known_hosts
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Deploy on host
run: |
ssh your_user@host.docker.internal << 'EOF'
cd /path/to/your/project
git pull
# 可以在这里添加重启服务的命令,例如:
# docker compose down && docker compose up -d
EOF
Workflow 技巧解析
1. 关于 SSH 连接宿主机
在 Docker 的默认 bridge 模式下,宿主机在容器内的地址通常是 host.docker.internal(由 extra_hosts 映射)。因此我们使用 ssh your_user@host.docker.internal 进行连接。你也可以根据实际网络环境替换为 172.17.0.1 等网关 IP。
2. 密钥管理
千万不要把私钥硬编码在 YAML 文件中!请在 Gitea 仓库的 Settings -> Secrets 中添加一个名为 SSH_PRIVATE_KEY 的 Secret,内容为你的私钥内容。Workflow 通过 ${{ secrets.SSH_PRIVATE_KEY }} 引用。
3. ssh-keyscan 的重要性
第一次 SSH 连接时,会有交互式的指纹确认。在自动化环境中,这会导致脚本卡死。使用 ssh-keyscan 预先将宿主机的公钥写入 known_hosts,是解决这个问题的标准做法。
常见问题与排错
-
Runner 显示离线?
检查 GITEA_INSTANCE_URL 是否能在 Runner 容器内连通。可以尝试 docker exec -it nas_gitea_runner curl http://gitea:3000。
-
Job 卡在 Pulling image 阶段?
检查代理配置。确保 Runner 的环境变量代理地址是正确的,并且宿主机防火墙放行了该端口。
-
Permission Denied (publickey)?
检查 chmod 600 ~/.ssh/id_ed25519 是否执行成功,以及 Secrets 中的私钥内容是否完整(包含 -----BEGIN OPENSSH PRIVATE KEY-----)。
总结
通过 Gitea + Act Runner,我们仅仅用了不到 100 行的配置,就拥有了一套功能强大、数据私有、且完全免费的 CI/CD 系统。
这套方案非常适合:
- 个人服务器的自动化部署。
- 企业内部不想暴露给公网的代码管理。
- 对 GitHub 速率限制感到烦恼的开发者。
希望这篇教程对你有帮助,如果有任何问题,欢迎在评论区留言讨论!
安全提示:发布前请务必完成以下检查:
- 将
DB_PASSWD 替换为强密码。
- 将
GITEA_RUNNER_REGISTRATION_TOKEN 替换为你的实际注册令牌(可在 Gitea 管理后台生成)。
- 将
HTTP_PROXY / HTTPS_PROXY 替换为你的实际代理地址(如有)。
- Workflow 中的
your_user 和 /path/to/your/project 替换为实际用户名和项目路径。
- 建议使用
.env 文件配合 docker compose 管理敏感变量,避免硬编码在配置文件中。
手把手教程:用 Docker Compose 搭建私有 CI/CD 平台(Gitea + Act Runner)
前言
在云原生时代,GitHub Actions 几乎成为了 CI/CD 的标准。但对于个人开发者、私有项目或者内网开发环境来说,将数据托管在 GitHub 并不总是最佳选择。
你是否想过拥有一套完全私有的、类似 GitHub Actions 体验的 CI/CD 系统?答案是肯定的。Gitea 搭配 Act Runner,可以让你在自己的服务器(甚至是 NAS)上轻松实现这一目标。
本文将基于我个人的实战配置,带你一步步搭建这套系统,并实现一个自动登录宿主机执行部署脚本的 Workflow。
架构概览
在开始之前,我们先了解一下我们的部署架构:
/var/run/docker.sock,在接收到任务后,动态创建新的"作业容器(Job Container)"来运行 Workflow。这保证了每次构建环境的纯净和隔离。第一步:编写 Docker Compose 配置
我们将使用
docker-compose.yml来统一管理 Gitea 和 Runner。以下是完整的配置文件,我将重点解释其中的关键参数:
配置详解
1. 代理设置 (HTTP_PROXY)
在国内环境,
act_runner在运行过程中需要下载node镜像或其他 Actions 插件。如果不配置代理,很容易导致超时失败。请将your_proxy_ip:port替换为你局域网内的代理地址。2. Docker Socket 挂载
- /var/run/docker.sock:/var/run/docker.sock是灵魂所在。它允许 Runner 容器控制宿主机的 Docker,从而在宿主机上创建 Job 容器。3. extra_hosts
host.docker.internal:host-gateway这一行非常有用。它使得 Runner 内部可以通过host.docker.internal这个域名访问到宿主机。虽然我们在下面的 Workflow 中用了 IP,但在某些复杂网络环境下,这个 Host 映射是救命稻草。4. Runner Labels
ubuntu-latest:docker://node:16-bullseye意味着:当 Workflow 中指定runs-on: ubuntu-latest时,Runner 会拉取node:16-bullseye镜像作为执行环境。第二步:启动服务
将上面的
docker-compose.yml保存后,在同级目录下执行:检查状态:
你应该能看到
nas_gitea和nas_gitea_runner都在运行中。此时访问
http://<你的服务器IP>:13000,完成 Gitea 的初始化安装。进入管理后台,你应该能看到 Runner 已经在线。第三步:编写 Workflow(自动化部署)
现在,我们在 Gitea 仓库中创建
.gitea/workflows/deploy.yml。我们的目标是:当代码推送到 master 分支时,自动登录到宿主机,拉取最新代码并重启应用。
Workflow 技巧解析
1. 关于 SSH 连接宿主机
在 Docker 的默认
bridge模式下,宿主机在容器内的地址通常是host.docker.internal(由extra_hosts映射)。因此我们使用ssh your_user@host.docker.internal进行连接。你也可以根据实际网络环境替换为172.17.0.1等网关 IP。2. 密钥管理
千万不要把私钥硬编码在 YAML 文件中!请在 Gitea 仓库的
Settings -> Secrets中添加一个名为SSH_PRIVATE_KEY的 Secret,内容为你的私钥内容。Workflow 通过${{ secrets.SSH_PRIVATE_KEY }}引用。3. ssh-keyscan 的重要性
第一次 SSH 连接时,会有交互式的指纹确认。在自动化环境中,这会导致脚本卡死。使用
ssh-keyscan预先将宿主机的公钥写入known_hosts,是解决这个问题的标准做法。常见问题与排错
Runner 显示离线?
检查
GITEA_INSTANCE_URL是否能在 Runner 容器内连通。可以尝试docker exec -it nas_gitea_runner curl http://gitea:3000。Job 卡在 Pulling image 阶段?
检查代理配置。确保 Runner 的环境变量代理地址是正确的,并且宿主机防火墙放行了该端口。
Permission Denied (publickey)?
检查
chmod 600 ~/.ssh/id_ed25519是否执行成功,以及 Secrets 中的私钥内容是否完整(包含-----BEGIN OPENSSH PRIVATE KEY-----)。总结
通过 Gitea + Act Runner,我们仅仅用了不到 100 行的配置,就拥有了一套功能强大、数据私有、且完全免费的 CI/CD 系统。
这套方案非常适合:
希望这篇教程对你有帮助,如果有任何问题,欢迎在评论区留言讨论!