chore: add deploy script and deployment docs
This commit is contained in:
@@ -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"
|
||||||
@@ -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"
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user