# Docker 镜像构建与导出

本文档说明如何在本地或 CI 机器上**构建** `api` / `player` / `admin` 三个生产镜像，并**导出**为 tar 包，便于上传到服务器离线加载部署。

> 全栈部署流程见上级文档：[Docker部署指南.md](../Docker部署指南.md)

---

## 一、脚本位置

脚本与本文档同目录 `docs/docker/`：

| 文件 | 适用环境 |
|------|----------|
| `docs/docker/build-and-export-images.bat` | Windows（CMD，构建全部） |
| `docs/docker/build-and-export-api.bat` | Windows（仅 api，可双击） |
| `docs/docker/build-and-export-player.bat` | Windows（仅 player，可双击） |
| `docs/docker/build-and-export-admin.bat` | Windows（仅 admin，可双击） |
| `docs/docker/build-and-export-images.ps1` | Windows（PowerShell） |
| `docs/docker/build-and-export-images.sh` | Linux / macOS / Git Bash |

两个脚本行为一致：在**项目根目录**执行 compose 构建 → 导出 tar（默认 tag 为 **`latest`**），并生成同名 `.manifest.txt`。

**默认 tag 为 `latest`**，服务器 `docker load` 后可直接 `compose up`，无需再 `docker tag`。版本追溯见 manifest 里的 `git_commit`。若需保留多版本 tag，可传 `--tag v1.2.3`。

---

## 二、前置条件

1. 已安装 **Docker** 与 **Docker Compose v2**（`docker compose`）
2. 项目根目录存在 `docker-compose.prod.yml`
3. 环境变量文件（二选一）：
   - **推荐**：`.env.docker`（从 `.env.docker.example` 复制并修改）
   - 若无 `.env.docker`，脚本会回退使用 `.env.docker.example` 并给出警告

生产环境务必在 `.env.docker` 中配置：

- `POSTGRES_PASSWORD`、`JWT_SECRET`
- `IMAGE_TAG`、`BIND_ADDR`、`RUN_MIGRATIONS_ON_START` 保持 `.env.docker.example` 默认即可，部署脚本会按 `--tag` 写回真实版本
- `CHUANGLAN_ACCOUNT`、`CHUANGLAN_PASSWORD`（短信注册）
- `SEED_DATABASE=false`（生产建议保持 false，由部署脚本按需一次性 seed）

---

## 三、使用方法

### Windows

**推荐（CMD / 双击）：** 在项目根目录打开命令提示符，或直接双击对应 bat：

```bat
docs\docker\build-and-export-images.bat
docs\docker\build-and-export-api.bat
docs\docker\build-and-export-player.bat
docs\docker\build-and-export-admin.bat
```

单服务 bat 等价于 `build-and-export-images.bat --service <name>`，其余参数（`--use-cache`、`--export-only`、`--tag` 等）照常追加即可，例如：

```bat
docs\docker\build-and-export-admin.bat --use-cache
```

PowerShell 也可用：

```powershell
.\docs\docker\build-and-export-images.ps1
```

### Linux / Git Bash

在项目根目录：

```bash
chmod +x docs/docker/build-and-export-images.sh
./docs/docker/build-and-export-images.sh
```

### 可选参数

| BAT / PowerShell | Bash | 说明 |
|----------------|------|------|
| `--service admin` / `-Service admin` | `--service admin` | 只构建/导出单个服务（`api` / `player` / `admin` / `all`） |
| `--tag v1.2.3` / `-Tag v1.2.3` | `--tag v1.2.3` | 指定镜像 tag；**默认 `latest`** |
| （默认） | （默认） | `--no-cache` 全量构建，适合发版 |
| `--use-cache` / `-UseCache` | `--use-cache` | 使用 Docker 缓存，构建更快 |
| `--export-only` / `-ExportOnly` | `--export-only` | 跳过构建，仅导出已有指定 tag 镜像 |
| `--output my.tar` / `-Output my.tar` | `--output my.tar` | 自定义导出文件名 |

默认导出文件名：

| 范围 | 默认 tar |
|------|----------|
| 全部 | `thebet365-images-latest.tar` |
| 仅 admin | `thebet365-admin-latest.tar` |
| 仅 api | `thebet365-api-latest.tar` |
| 仅 player | `thebet365-player-latest.tar` |

示例：只打包 admin（含邀请链接 `VITE_PLAYER_URL`）

```bat
docs\docker\build-and-export-images.bat --service admin
```

示例：仅重新导出已有镜像

```bat
docs\docker\build-and-export-images.bat --export-only
```

```powershell
.\docs\docker\build-and-export-images.ps1 -ExportOnly
```

```bash
./docs/docker/build-and-export-images.sh --export-only
```

---

## 四、构建产物

| 镜像名 | 说明 |
|--------|------|
| `thebet365-api:<tag>` | NestJS API（迁移由部署脚本执行） |
| `thebet365-player:<tag>` | 玩家前台（Nginx 静态资源） |
| `thebet365-admin:<tag>` | 管理后台（Nginx 静态资源） |

导出文件默认路径：

```text
<项目根目录>/thebet365-images-<tag>.tar
<项目根目录>/thebet365-images-<tag>.manifest.txt
```

这些文件已加入 `.gitignore`，**请勿提交到 Git**。manifest 会记录 tag、构建时间、Git commit、镜像 ID 和 tar 的 SHA-256，便于服务器核对发布包。

---

## 五、上传到服务器并部署

### 1. 上传

将以下内容传到服务器同一目录（如 `/www/wwwroot/thebet365`）：

- `thebet365-images-<tag>.tar`
- `thebet365-images-<tag>.manifest.txt`
- `docker-compose.prod.yml`
- `.env.docker`（或服务器上已有配置）
- `docker/nginx/` 等 compose 依赖目录（若仅 load 镜像、不 rebuild，compose 文件仍需要）

可用 SCP、宝塔文件管理、rsync 等。

### 2. 首次部署

```bash
cd /www/wwwroot/thebet365
chmod +x scripts/*.sh
./scripts/deploy-first.sh --images thebet365-images-v1.2.3.tar --tag v1.2.3
```

后续更新同一个服务器时：

```bash
./scripts/deploy-update.sh --images thebet365-images-v1.2.3.tar --tag v1.2.3
```

更新脚本会先备份数据库与 uploads，再用新 API 镜像执行 `prisma migrate deploy`，最后替换运行中的容器并等待健康检查通过。

### 3. 验证

```bash
docker compose -f docker-compose.prod.yml ps
docker logs thebet365-api --tail 50
```

浏览器访问（端口以 `.env.docker` 为准）：

- 玩家端：经宝塔反代访问，或服务器本机 `http://127.0.0.1:8082`
- 管理端：经宝塔反代访问，或服务器本机 `http://127.0.0.1:8081`

---

## 六、与「服务器上直接 build」的区别

| 方式 | 优点 | 缺点 |
|------|------|------|
| **本地 build + 导出 tar** | 不占用服务器 CPU/内存；有 tag 与 manifest，可重复部署同一包 | 需上传较大 tar（约 200–300 MB） |
| **服务器 `docker compose build`** | 无需传 tar | 首次/全量构建慢，小内存机器易失败 |

发版推荐流程：**本地或构建机执行脚本 → 上传 tar + manifest → 服务器执行 `deploy-update.sh --images thebet365-images-<tag>.tar --tag <tag>`**。

---

## 七、常见问题

### 1. 构建时提示 `CHUANGLAN_* variable is not set`

仅为 **警告**，不影响镜像构建；运行时请在 `.env.docker` 中补全创蓝配置，否则短信验证码无法发送。

### 2. player / admin 构建失败 `ENOENT ... public/球员`

旧包残留中文目录。清理后重试：

```bash
find packages/shared/public -mindepth 1 -maxdepth 1 -type d \
  ! -name flags ! -name players -exec rm -rf {} +
```

### 3. `docker load` 后部署仍找不到镜像

确保上传的 tar 中包含 `thebet365-api:<tag>`、`thebet365-player:<tag>`、`thebet365-admin:<tag>`，并且服务器执行部署时传入同一个 `--tag <tag>`。

### 4. API 启动后不断重启

```bash
docker logs thebet365-api
```

常见原因：数据库未就绪、`DATABASE_URL` 与 `POSTGRES_PASSWORD` 不一致、迁移失败。

---

## 八、相关文件

```text
thebet365/
├── docker-compose.prod.yml
├── .env.docker.example
├── thebet365-images-<tag>.tar       # 导出产物（默认，已 gitignore）
├── thebet365-images-<tag>.manifest.txt
├── docker/
│   ├── api/Dockerfile
│   ├── player/Dockerfile
│   ├── admin/Dockerfile
│   └── nginx/
└── docs/
    ├── Docker部署指南.md
    └── docker/
        ├── 镜像构建与导出.md        # 本文档
        ├── build-and-export-images.bat
        ├── build-and-export-images.ps1
        └── build-and-export-images.sh
```
