# CI/CD 设计：本地构建部署（no-push）

> 日期：2026-07-08
> 状态：已批准，待实施
> 替代方案：[2026-07-08 之前的 docker.yml + deploy.yml push 方案](../../.github/workflows/)

## 1. 背景

### 1.1 现状问题

- `node-with-docker:22` 自定义镜像已丢失，原方案依赖它无法运行
- 推送到 Gitea Registry 的拉取速度极慢，影响部署效率
- 用户习惯本地构建测试后再推送，无需保留历史构建产物
- 微服务架构下，拆分多个 workflow 文件（ci-ts/ci-go/docker/deploy）使流程碎片化

### 1.2 目标

- 不依赖任何自建镜像，全部使用官方镜像
- 不推送镜像到 registry，构建即部署
- 单文件管理整个 CI/CD 流程
- 支持回滚（不依赖 registry tag）
- 提供一次性预拉所有镜像的 compose 文件

## 2. 设计

### 2.1 架构总览

```
PR/push 触发
   │
   ├─ quality-ts job     (container: node:22-alpine)         并行
   ├─ quality-go job     (container: golang:1.22-alpine)     并行
   ├─ quality-proto job  (container: bufbuild/buf:latest)    并行
   │
   └─ deploy job         (container: docker:25-git)          仅 push main, needs: [quality-*]
       挂载 /var/run/docker.sock（DooD）
       ├─ docker compose up --build（本地构建 3 个服务）
       └─ 健康检查轮询
```

### 2.2 文件结构

```
.github/workflows/
└─ ci.yml                          # 唯一的 CI/CD 文件

infra/
├─ docker-compose.deploy.yml       # 改造：build: 替代 image:
├─ docker-compose.tools.yml        # 新建：预拉所有 CI 需要的镜像
└─ deploy.env.example              # 保留
```

### 2.3 ci.yml 设计

**单文件多 job**：

- `quality-ts`：PR+push 都跑，container: node:22-alpine，pnpm install → lint → typecheck → test → build
- `quality-go`：PR+push 都跑，container: golang:1.22-alpine，go mod download → vet → build → test
- `quality-proto`：PR+push 都跑，container: bufbuild/buf:latest，buf lint + buf breaking（仅 PR）
- `deploy`：仅 push main 或 workflow_dispatch 触发，needs: [quality-ts, quality-go, quality-proto]

**deploy job 关键配置**：

- `container: docker:25-git`（官方镜像，自带 docker CLI + compose v2 + git）
- `options: --volume /var/run/docker.sock:/var/run/docker.sock`（DooD 模式）
- 流程：checkout → cp compose 文件到 /opt/edu/ → docker compose up --build → 健康检查

**回滚**：

- `workflow_dispatch` 支持 `commit_sha` 输入
- checkout 时使用指定 commit SHA
- 重新 build + deploy

### 2.4 docker-compose.deploy.yml 改造

所有服务从 `image:` 改为 `build:`：

- `api-gateway`：`build: ./services/api-gateway`
- `classes`：`build: { context: ., dockerfile: services/classes/Dockerfile }`（monorepo 上下文）
- `teacher-portal`：`build: { context: ., dockerfile: apps/teacher-portal/Dockerfile }`（monorepo 上下文）

删除 `IMAGE_TAG` 环境变量依赖。`docker compose up --build` 自动判断变更的服务。

### 2.5 docker-compose.tools.yml（预拉镜像）

用于一次性拉取所有 CI/构建需要的镜像到本地：

- CI 运行时：node:22-alpine、golang:1.22-alpine、bufbuild/buf:latest、docker:25-git
- 服务构建基础：node:20-alpine、golang:1.22-alpine、alpine:3.20
- 开发基础设施：mysql:8.0、redis:7-alpine

用法：`docker compose -f infra/docker-compose.tools.yml pull`

### 2.6 回滚策略

| 方式       | 操作                                                | 适用场景         |
| ---------- | --------------------------------------------------- | ---------------- |
| git revert | `git revert <bad-commit> && git push` → 自动触发 CI | 代码回滚（推荐） |
| 手动触发   | Actions → ci.yml → Run workflow → 填 commit_sha     | 部署特定版本     |

### 2.7 actrunner 配置

```toml
container:
  valid_volumes:
    - /var/run/docker.sock
```

## 3. 实施步骤

1. 删除现有 4 个 workflow 文件（ci-ts.yml、ci-go.yml、ci-proto.yml、docker.yml、deploy.yml）
2. 创建 `.github/workflows/ci.yml`（合并单 workflow）
3. 改造 `infra/docker-compose.deploy.yml`（build 替代 image）
4. 创建 `infra/docker-compose.tools.yml`（预拉镜像）
5. 更新 `.trae/rules/project_rules.md` §15（no-push 模式规范）
6. 更新 `docs/standards/cicd-runbook.md`（删除 registry 章节，新增本地构建章节）
7. 提交并推送

## 4. 与旧方案对比

| 维度            | 旧方案（push 到 registry） | 新方案（no-push 本地构建）     |
| --------------- | -------------------------- | ------------------------------ |
| workflow 文件数 | 4 个                       | 1 个                           |
| 镜像推送        | push 到 Gitea Registry     | 不推送                         |
| 部署方式        | compose pull + up          | compose up --build             |
| 自建镜像        | 依赖 node-with-docker:22   | 全用官方镜像                   |
| 回滚            | 切换 registry tag          | git revert / workflow_dispatch |
| 构建产物保留    | registry 保留历史          | 不保留                         |

## 5. 风险与缓解

| 风险                      | 缓解                                           |
| ------------------------- | ---------------------------------------------- |
| 本地镜像被清理后无法回滚  | 回滚走 git revert + 重新 build，不依赖镜像缓存 |
| docker.sock 挂载安全风险  | actrunner 仅在部署服务器运行，已隔离           |
| 构建慢                    | layer cache 在宿主机本地，未变更的层秒过       |
| Gitea workflow_run 不支持 | 用 needs 串联，不用 workflow_run               |
