From 0edd4c89699e7a89956d931946b7f2346b52152d Mon Sep 17 00:00:00 2001 From: dongsk Date: Fri, 28 Aug 2026 17:30:07 +0800 Subject: [PATCH] chore: add deploy script and deployment docs --- deploy.sh | 34 ++++++++ docs/deployment.md | 206 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100755 deploy.sh create mode 100644 docs/deployment.md diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..13ef7a8 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,34 @@ +#!/bin/bash +set -e + +# ── 配置项 ────────────────────────────────────────── +IMAGE_NAME="ai-platform:1.0.0" +CONTAINER_NAME="ai-platform" +PORT=8901 +BACKEND_URL="${BACKEND_URL:-http://host.docker.internal:8000}" +# ─────────────────────────────────────────────────── + +echo ">>> 构建镜像 ${IMAGE_NAME} ..." +sudo docker build -t "$IMAGE_NAME" . + +echo ">>> 停止并移除旧容器 ..." +sudo docker stop "$CONTAINER_NAME" 2>/dev/null || true +sudo docker rm "$CONTAINER_NAME" 2>/dev/null || true + +echo ">>> 启动新容器 ..." +sudo docker run -d \ + --name "$CONTAINER_NAME" \ + -p "$PORT:8901" \ + --add-host=host.docker.internal:host-gateway \ + -e BACKEND_URL="$BACKEND_URL" \ + "$IMAGE_NAME" + +echo ">>> 等待 nginx 启动 ..." +sleep 2 + +echo ">>> 验证 ..." +sudo docker exec "$CONTAINER_NAME" curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:8901/ + +echo "" +echo ">>> 部署完成: http://localhost:$PORT" +echo ">>> 后端代理: $BACKEND_URL" diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..9ed611d --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,206 @@ +# AI Platform 打包部署指南 + +## 项目概况 + +| 项目 | 说明 | +|------|------| +| 项目名称 | ai-platform | +| 前端框架 | React 18 + TypeScript + Vite 8 | +| UI 库 | Ant Design 5 + Tailwind CSS 3 | +| 构建产物 | 静态文件(`dist/`),由 nginx 提供服务 | +| 容器化 | Docker 多阶段构建(builder → nginx:alpine) | +| 部署端口 | 8901 | +| 后端代理 | `http://host.docker.internal:8000`(可运行时覆盖) | + +## 目录结构要点 + +``` +ai-platform/ +├── Dockerfile # 多阶段构建:node:20-alpine 构建 → nginx:alpine 运行 +├── nginx.conf # nginx 配置模板(支持 ${BACKEND_URL} 环境变量替换) +├── .dockerignore # 排除 node_modules / dist / .git / *.md 等 +├── package.json # build 脚本:tsc -b && vite build +├── vite.config.ts # 开发代理配置(/api/v1, /health, /bexapi, /iam-api) +├── index.html # 入口 HTML +├── src/ # 源码 +├── public/ # 静态资源 +└── docs/ # 文档(本文件所在目录) +``` + +## Docker 镜像构建 + +### Dockerfile 构建流程 + +1. **Stage 1 — builder(node:20-alpine)** + - 复制 `package.json` + `package-lock.json`,执行 `npm install` + - 复制全部源码,执行 `npm run build`(即 `tsc -b && vite build`) + - 产物输出到 `/app/dist` + +2. **Stage 2 — 运行时(nginx:alpine)** + - 将 builder 阶段的 `/app/dist` 复制到 `/usr/share/nginx/html` + - 复制 `nginx.conf` 到 `/etc/nginx/templates/default.conf.template`(nginx 入口脚本会通过 `envsubst` 替换其中的 `${BACKEND_URL}`) + - 默认环境变量 `BACKEND_URL=http://host.docker.internal:8000` + - 暴露端口 8901 + +### 构建命令 + +```bash +sudo docker build -t ai-platform:1.0.0 . +``` + +构建过程利用了 Docker 缓存层:如果 `package.json` / `package-lock.json` 未变,`npm install` 层会命中缓存,仅重新执行源码复制和构建。 + +## 容器部署 + +### 启动命令 + +```bash +sudo docker run -d \ + --name ai-platform \ + -p 8901:8901 \ + --add-host=host.docker.internal:host-gateway \ + -e BACKEND_URL=http://host.docker.internal:8000 \ + ai-platform:1.0.0 +``` + +### 参数说明 + +| 参数 | 说明 | +|------|------| +| `-d` | 后台运行 | +| `--name ai-platform` | 容器名称 | +| `-p 8901:8901` | 宿主机 8901 端口映射到容器 8901 | +| `--add-host=host.docker.internal:host-gateway` | **必须添加**,否则 nginx 启动时无法解析 `host.docker.internal`,容器会立即退出(exit code 1) | +| `-e BACKEND_URL=...` | 后端 API 地址,运行时可覆盖。默认 `http://host.docker.internal:8000` | + +> **踩坑记录**:Docker 29.x 版本中,`host.docker.internal` 不会自动解析,必须通过 `--add-host=host.docker.internal:host-gateway` 显式添加。如果不加此参数,nginx 启动报错:`host not found in upstream "host.docker.internal"`,容器退出码为 1。 + +### 重新部署(完整流程) + +```bash +# 1. 构建新镜像 +sudo docker build -t ai-platform:1.0.0 . + +# 2. 停止并移除旧容器 +sudo docker stop ai-platform +sudo docker rm ai-platform + +# 3. 启动新容器 +sudo docker run -d \ + --name ai-platform \ + -p 8901:8901 \ + --add-host=host.docker.internal:host-gateway \ + -e BACKEND_URL=http://host.docker.internal:8000 \ + ai-platform:1.0.0 +``` + +### 验证部署 + +```bash +# 检查容器运行状态 +sudo docker ps --filter "name=ai-platform" + +# 从容器内部验证 nginx 响应 +sudo docker exec ai-platform curl -s -o /dev/null -w "HTTP %{http_code}" http://localhost:8901/ +# 预期输出:HTTP 200 + +# 查看页面内容 +sudo docker exec ai-platform curl -s http://localhost:8901/ + +# 查看 nginx 日志 +sudo docker logs ai-platform +``` + +## nginx 配置说明 + +`nginx.conf` 作为模板文件放在 `/etc/nginx/templates/default.conf.template`,nginx 容器入口脚本会自动用 `envsubst` 将 `${BACKEND_URL}` 替换为环境变量值,输出到 `/etc/nginx/conf.d/default.conf`。 + +### 路由规则 + +| 路径 | 代理目标 | 说明 | +|------|----------|------| +| `/api/v1/` | `${BACKEND_URL}/api/v1/` | 后端 API,支持 SSE 长连接(关闭 buffering,300s 超时) | +| `/health` | `${BACKEND_URL}/health` | 健康检查 | +| `/bexapi/` | `https://bexell-scheduling.dongsk.top/api/` | Bexell 排程 API | +| `/iam-api/` | `https://iam.digiwincloud.com.cn/` | 鼎捷云 IAM 登录服务 | +| `/` | `try_files $uri $uri/ /index.html` | SPA 路由 fallback | +| `/assets/` | 本地静态文件 | 长缓存 1 年,`Cache-Control: public, immutable` | +| `*.svg/png/jpg/...` | 本地静态文件 | 缓存 30 天 | + +### 开发环境 vs 生产环境代理对照 + +开发时由 Vite dev server 代理(`vite.config.ts`),生产时由 nginx 代理(`nginx.conf`),两者路径需保持一致: + +| 路径 | Vite target | nginx proxy_pass | +|------|------------|-------------------| +| `/api/v1` | `http://localhost:8000` | `${BACKEND_URL}/api/v1/` | +| `/health` | `http://localhost:8000` | `${BACKEND_URL}/health` | +| `/bexapi` | `https://bexell-scheduling.dongsk.top`(rewrite → `/api`) | `https://bexell-scheduling.dongsk.top/api/` | +| `/iam-api` | `https://iam.digiwincloud.com.cn`(rewrite → 去前缀) | `https://iam.digiwincloud.com.cn/` | + +## 常见问题排查 + +### 容器启动后立即退出(exit code 1) + +**原因**:nginx 无法解析 `host.docker.internal`。 + +**解决**:启动时添加 `--add-host=host.docker.internal:host-gateway`。 + +**验证**:`sudo docker logs ai-platform` 查看是否有 `host not found in upstream` 错误。 + +### 构建时 TypeScript 编译失败 + +`npm run build` 先执行 `tsc -b` 类型检查,类型错误会导致构建中断。需修复所有 TS 错误后重新构建。 + +### 构建产物体积过大 + +当前主 chunk 约 1.9MB(gzip 后 581KB),Vite 会输出警告。如需优化: + +- 使用动态 `import()` 对路由模块做懒加载(代码分割) +- 在 `vite.config.ts` 中配置 `build.rollupOptions.output.manualChunks` 手动分包 +- 调整 `build.chunkSizeWarningLimit` 提高警告阈值(仅消除警告,不减小体积) + +### 端口被占用 + +```bash +# 查看占用 8901 端口的进程 +sudo lsof -i :8901 + +# 停止旧容器后释放端口 +sudo docker stop ai-platform +``` + +## 快速部署脚本 + +将以下内容保存为 `deploy.sh` 即可一键重新打包部署: + +```bash +#!/bin/bash +set -e + +IMAGE_NAME="ai-platform:1.0.0" +CONTAINER_NAME="ai-platform" +PORT=8901 +BACKEND_URL="${BACKEND_URL:-http://host.docker.internal:8000}" + +echo ">>> 构建镜像..." +sudo docker build -t "$IMAGE_NAME" . + +echo ">>> 停止旧容器..." +sudo docker stop "$CONTAINER_NAME" 2>/dev/null || true +sudo docker rm "$CONTAINER_NAME" 2>/dev/null || true + +echo ">>> 启动新容器..." +sudo docker run -d \ + --name "$CONTAINER_NAME" \ + -p "$PORT:8901" \ + --add-host=host.docker.internal:host-gateway \ + -e BACKEND_URL="$BACKEND_URL" \ + "$IMAGE_NAME" + +echo ">>> 验证..." +sleep 2 +sudo docker exec "$CONTAINER_NAME" curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:8901/ + +echo ">>> 部署完成: http://localhost:$PORT" +```