Skip to content

手把手教程:用 Docker Compose 搭建私有 CI/CD 平台(Gitea + Act Runner) #4

Description

@unclay

手把手教程:用 Docker Compose 搭建私有 CI/CD 平台(Gitea + Act Runner)

前言

在云原生时代,GitHub Actions 几乎成为了 CI/CD 的标准。但对于个人开发者、私有项目或者内网开发环境来说,将数据托管在 GitHub 并不总是最佳选择。

你是否想过拥有一套完全私有的、类似 GitHub Actions 体验的 CI/CD 系统?答案是肯定的。Gitea 搭配 Act Runner,可以让你在自己的服务器(甚至是 NAS)上轻松实现这一目标。

本文将基于我个人的实战配置,带你一步步搭建这套系统,并实现一个自动登录宿主机执行部署脚本的 Workflow。


架构概览

在开始之前,我们先了解一下我们的部署架构:

  1. Gitea:轻量级的自托管 Git 服务(类似私有版 GitHub)。
  2. Act Runner:Gitea 官方的 Actions 运行器,它负责监听 Gitea 的事件并执行业务逻辑。
  3. 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 保存后,在同级目录下执行:

docker compose up -d

检查状态:

docker ps

你应该能看到 nas_giteanas_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,是解决这个问题的标准做法。


常见问题与排错

  1. Runner 显示离线?
    检查 GITEA_INSTANCE_URL 是否能在 Runner 容器内连通。可以尝试 docker exec -it nas_gitea_runner curl http://gitea:3000

  2. Job 卡在 Pulling image 阶段?
    检查代理配置。确保 Runner 的环境变量代理地址是正确的,并且宿主机防火墙放行了该端口。

  3. Permission Denied (publickey)?
    检查 chmod 600 ~/.ssh/id_ed25519 是否执行成功,以及 Secrets 中的私钥内容是否完整(包含 -----BEGIN OPENSSH PRIVATE KEY-----)。


总结

通过 Gitea + Act Runner,我们仅仅用了不到 100 行的配置,就拥有了一套功能强大、数据私有、且完全免费的 CI/CD 系统。

这套方案非常适合:

  • 个人服务器的自动化部署。
  • 企业内部不想暴露给公网的代码管理。
  • 对 GitHub 速率限制感到烦恼的开发者。

希望这篇教程对你有帮助,如果有任何问题,欢迎在评论区留言讨论!


安全提示:发布前请务必完成以下检查:

  1. DB_PASSWD 替换为强密码。
  2. GITEA_RUNNER_REGISTRATION_TOKEN 替换为你的实际注册令牌(可在 Gitea 管理后台生成)。
  3. HTTP_PROXY / HTTPS_PROXY 替换为你的实际代理地址(如有)。
  4. Workflow 中的 your_user/path/to/your/project 替换为实际用户名和项目路径。
  5. 建议使用 .env 文件配合 docker compose 管理敏感变量,避免硬编码在配置文件中。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions