Files
iotlcm/docs/deploy/index.md
T
Dai0814 07ddaa9968 docs: 新增部署手册页并修复容器部署白屏问题
- 新增 docs/deploy/ 部署手册:推荐环境(Node 22 / nginx / Docker)、
  编译方法(npm ci + build)、四类部署方式(Docker+nginx 推荐路径、
  静态服务器、对象存储/CDN、内网离线),含路径前缀要点与环境对照表
- 修复 docker/nginx.conf:base=/docs/ 使产物资源引用为绝对路径,
  原配置从根路径服务导致资源全部 404(白屏)。改用 alias 剥离前缀,
  并加 / -> /docs/ 重定向
- README 补部署指引与路径前缀告警,修正 dev 入口地址为 /docs/
- 导航与首页增加「部署手册」入口
2026-10-26 16:51:33 +08:00

444 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: 部署手册
description: iotLCM 文档站的推荐环境、编译方法与部署方式
outline: deep
---
# 部署手册
本页说明 iotLCM 文档站(`iotlcm-docs`)的运行环境要求、编译方法与部署方式。文档站是**纯静态站点**,由 VitePress 1.6 构建,产物不含任何服务端运行时。
::: tip 一句话结论
生产推荐用 **Docker + nginx**(Node 只在构建阶段需要);只有一台机器、不想装 Docker 时,用 **Node 构建 + 任意 Web 服务器托管静态产物**。
:::
## 环境要求
### 构建环境(必需)
| 项 | 推荐 | 最低 | 说明 |
| --- | --- | --- | --- |
| Node.js | **22 LTS** | 18 | VitePress 依赖的 Vite 5 要求 `^18.0.0 \|\| >=20.0.0` |
| 包管理器 | npm 10(随 Node 22) | npm 9 | 仓库带 `package-lock.json`,用 `npm ci` 精确安装 |
| 内存 | 2 GB | 1 GB | 构建峰值约 500 MB |
| 磁盘 | 2 GB 可用 | 1 GB | `node_modules` 约 103 MB,产物 25 MB |
| 网络 | 可访问 npm 源 | — | 仓库 `.npmrc` 已指向 `registry.npmmirror.com` |
### 运行环境(只需静态托管)
构建完成后**不再需要 Node**。运行侧只需要一个能提供静态文件的 Web 服务器(nginx / Apache / Caddy / 对象存储均可)。
| 项 | 推荐 | 说明 |
| --- | --- | --- |
| 操作系统 | Linux x86_64(Debian 12 / Ubuntu 22.04+) | 任意系统均可,产物与平台无关 |
| Web 服务器 | nginx 1.27+ | 仓库自带 `docker/nginx.conf` |
| 容器运行时 | Docker 20+ / Docker Compose v2 | 仅容器方式需要 |
| 资源占用 | 内存 32 MB、磁盘 30 MB | 纯静态,压力极低 |
### 实测参考(Debian/Ubuntu + Node 22 + Docker 29)
| 操作 | 耗时 | 产物 |
| --- | --- | --- |
| `git clone` | 约 2 秒 | 单 commit |
| `npm ci` | 约 12 秒 | 128 个包 / 103 MB |
| `npm run build` | 约 4 秒 | 25 MB / 267 个文件 |
| `docker build`(冷,含拉镜像) | 约 2 分 18 秒 | 镜像 120 MB |
| `docker build`(热,镜像已缓存) | 约 10 秒 | 镜像 120 MB |
## 获取代码
```bash
git clone https://git.5iot.com/iotlcm-public/iotlcm-docs.git
cd iotlcm-docs
```
开源仓**不含 git 历史**,只有一个初始提交,这是预期行为:
```bash
git log --oneline
# 92f239f chore: 初始开源发布
```
## ⚠️ 部署前必读:路径前缀
这是本项目最容易出错的地方,**部署失败九成是这个原因**。
文档站配置里 `base: '/docs/'`(见 `.vitepress/config.mts`),因此构建产物中所有资源引用都是**绝对路径**:
```html
<link rel="stylesheet" href="/docs/assets/style.xxx.css">
<script src="/docs/assets/app.xxx.js"></script>
```
而产物目录 `.vitepress/dist` 是**平铺**的,`index.html`、`assets/` 直接在根,**没有 `docs/` 子目录**。
由此推出两条铁律:
1. **对外访问路径必须是 `/docs/`**,且服务器必须把 `/docs/` 前缀剥掉后再落到产物目录
2. 若想挂到别的前缀(如 `/manual/`),必须**先改 `base` 再重新构建**,不能只改服务器配置
::: warning 典型故障:白屏
如果配置成「从根路径直接服务产物」(`root` 指向 dist,访问 `/`),页面 HTML 会返回 200,但 `/docs/assets/*.js` 全部 404 → **浏览器白屏,没有任何报错提示**。
:::
三种正确做法,任选其一:
| 做法 | 适用 | 关键配置 |
| --- | --- | --- |
| nginx `alias` 剥前缀 | 容器部署(推荐) | `location /docs/ { alias /usr/share/nginx/html/; }` |
| 把产物放进 `docs/` 子目录 | 静态托管 / 对象存储 | `dist/` → `www/docs/` |
| 改 `base: '/'` 重新构建 | 独占域名根路径 | 改配置后必须重新 `npm run build` |
---
## 推荐环境部署:Docker + nginx
适用:生产环境、需要长期稳定运行、机器上不想装 Node。
仓库自带多阶段 `Dockerfile`:Node 阶段执行 `vitepress build`,最终镜像只含 nginx 与静态产物,**不含 Node 和 `node_modules`**。
### 构建镜像
```bash
SHA=$(git rev-parse --short HEAD)
docker build -t iotlcm-docs:$SHA .
```
用 commit sha 做标签,便于追溯与回滚;不建议只用 `latest`。
### 启动容器
```bash
docker run -d \
--name iotlcm-docs \
--restart unless-stopped \
-p 8019:80 \
iotlcm-docs:$SHA
```
### nginx 配置(关键)
`docker/nginx.conf` 用 `alias` 把 `/docs/` 前缀剥掉:
```nginx
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# 产物按 base=/docs/ 生成,资源引用形如 /docs/assets/xxx
location /docs/ {
alias /usr/share/nginx/html/;
try_files $uri $uri.html $uri/ $uri/index.html =404;
}
# 根路径跳到文档入口,避免访问 / 时白屏
location = / {
return 302 /docs/;
}
location / {
try_files $uri $uri.html $uri/ =404;
}
}
```
`try_files` 里的 `$uri.html` 与 `$uri/index.html` 是必需的:配置启用了 `cleanUrls: true`,页面 URL 不带 `.html` 后缀,需要服务器做内部映射。
### 部署后自检
```bash
# 1. 入口可访问(应返回 200)
curl -o /dev/null -s -w '%{http_code}\n' http://127.0.0.1:8019/docs/
# 2. 根路径应 302 到 /docs/
curl -o /dev/null -s -w '%{http_code}\n' http://127.0.0.1:8019/
# 3. 静态资源不能 404(最容易出问题的一步)
ASSET=$(curl -s http://127.0.0.1:8019/docs/ | grep -o '/docs/assets/style[^"]*\.css' | head -1)
curl -o /dev/null -s -w '%{http_code}\n' "http://127.0.0.1:8019$ASSET"
# 4. 抽查正文页面
curl -o /dev/null -s -w '%{http_code}\n' http://127.0.0.1:8019/docs/manual/web/
```
四项都返回 `200`(第 2 项返回 `302`)才算部署成功。
### 通过外层反向代理对外
生产常见做法:容器只监听 `127.0.0.1`,由外层 nginx 转发。
```nginx
upstream iotlcm_docs {
server 127.0.0.1:8019;
}
location = /docs {
return 301 /docs/;
}
location /docs/ {
proxy_pass http://iotlcm_docs; # 末尾不要加斜杠
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
::: danger proxy_pass 末尾斜杠
写成 `proxy_pass http://iotlcm_docs/;`(带末尾斜杠)会把 `/docs/` 前缀剥掉,转发到容器变成 `/`,导致资源全部 404 白屏。**必须不带末尾斜杠**。
:::
### docker compose
`docker-compose.yml` 中镜像名是占位值,需替换为自己的仓库地址:
```yaml
services:
docs:
image: your-registry/iotlcm/iotlcm-docs:${TAG:-latest}
container_name: iotlcm-docs
restart: unless-stopped
ports:
- "127.0.0.1:8019:80"
networks:
- iotlcm-network
networks:
iotlcm-network:
external: true
name: iotlcm-network
```
compose 文件声明 `iotlcm-network` 为外部网络,若不存在需先创建:
```bash
docker network create iotlcm-network
```
启动:
```bash
TAG=$(git rev-parse --short HEAD) docker compose up -d
```
---
## 其他环境
### 方式一:本地开发与预览(改文档时用)
```bash
npm ci
npm run dev
```
访问 **`http://127.0.0.1:5173/docs/`**。
::: warning 地址必须带 /docs/
`base: '/docs/'` 决定了 dev 服务器入口也在子路径下,直接访问 `http://127.0.0.1:5173/` 会 404。
:::
改动 `docs/` 下任意 `.md` 文件会热更新,无需重启。
需要让局域网内其他机器访问时,把监听地址改为 `0.0.0.0`:
```bash
npx vitepress dev . --host 0.0.0.0
# 然后访问 http://<本机IP>:5173/docs/
```
端口被占用时换端口:
```bash
npx vitepress dev . --port 5174
```
### 方式二:构建静态产物 + 任意 Web 服务器
这是**不使用 Docker** 时最通用的做法。
#### 编译
```bash
npm ci
npm run build
# 产物在 .vitepress/dist
```
本地验证构建结果(不要用于生产):
```bash
npm run preview
# http://127.0.0.1:4173/docs/
```
#### 部署到 nginx(子路径挂载,推荐)
把产物放到一个 `docs` 子目录,即可与 `base` 天然对齐,**无需 alias**:
```bash
sudo mkdir -p /var/www/iotlcm/www
sudo cp -r .vitepress/dist /var/www/iotlcm/www/docs
```
```nginx
server {
listen 80;
server_name _;
root /var/www/iotlcm/www;
location /docs/ {
try_files $uri $uri.html $uri/ $uri/index.html =404;
}
location = /docs {
return 301 /docs/;
}
}
```
此处 `root` 指向父目录、产物在 `docs/` 子目录下,`/docs/assets/app.js` 会自然映射到 `/var/www/iotlcm/www/docs/assets/app.js`,不需要 `alias`。
#### 部署到 nginx(根路径独占域名)
若文档站独占一个域名、需要挂在 `/`,必须**改 `base` 后重新构建**:
```bash
sed -i "s#base: '/docs/'#base: '/'#" .vitepress/config.mts
npm run build
```
```nginx
server {
listen 80;
server_name docs.example.com;
root /var/www/iotlcm-docs;
index index.html;
location / {
try_files $uri $uri.html $uri/ =404;
}
}
```
::: danger 不要只改服务器配置
`base` 与访问前缀必须一致。改了 nginx 却不改 `base`(或反之),一定会白屏。
:::
#### 部署到对象存储 / CDN
产物是纯静态文件,可直接上传到 S3 / OSS / MinIO 等:
```bash
npm run build
# 上传 .vitepress/dist 全部内容到桶内 docs/ 前缀下
```
要点:
- 上传时保持 `/docs/` 前缀(或改 `base` 后重新构建)
- 配置**默认首页**为 `docs/index.html`
- 配置 **404 页面**为 `docs/404.html`(产物已自带)
- `cleanUrls: true` 意味着页面 URL 不带 `.html`,需对象存储支持「无后缀映射到 .html」,否则需关闭 `cleanUrls` 重新构建
### 方式三:内网 / 离线环境
机器无法访问公网 npm 源时:
**方案 A:使用内网镜像源**
```bash
npm ci --registry=https://your-internal-registry/
```
**方案 B:在有网机器构建后只拷产物**
产物是纯静态文件,**不需要在目标机器上装 Node**:
```bash
# 有网机器
npm ci && npm run build
tar czf iotlcm-docs-dist.tar.gz -C .vitepress/dist .
# 目标机器
tar xzf iotlcm-docs-dist.tar.gz -C /var/www/iotlcm/www/docs
```
**方案 C:内网构建镜像**
`Dockerfile` 里基础镜像来自 Docker Hub(`node:22-alpine`、`nginx:1.27-alpine`),内网环境需先同步到内部镜像仓库,然后修改 `Dockerfile` 的 `FROM`:
```dockerfile
FROM your-registry/library/node:22-alpine AS builder
# ...
FROM your-registry/library/nginx:1.27-alpine
```
::: tip 已内置国内 apk 源
`Dockerfile` 的 builder 阶段已把 Alpine 源换成 `mirrors.aliyun.com`,无需额外处理。
:::
### 方式四:pnpm / yarn
仓库同时提供 `package-lock.json` 与 `pnpm-lock.yaml`。使用 pnpm:
```bash
pnpm install --frozen-lockfile
pnpm run build
```
npm 与 pnpm 二选一即可,**不要混用**(会产生不一致的 `node_modules`)。
---
## 环境对照表
| 场景 | Node | Docker | 访问路径 | 关键动作 |
| --- | --- | --- | --- | --- |
| 本地改文档 | 必需 | 不需要 | `/docs/` | `npm run dev` |
| 生产(推荐) | 仅构建阶段 | 必需 | `/docs/` | `docker build` + nginx alias |
| 静态托管(子路径) | 仅构建阶段 | 不需要 | `/docs/` | 产物放 `docs/` 子目录 |
| 静态托管(根路径) | 仅构建阶段 | 不需要 | `/` | 改 `base: '/'` 重新构建 |
| 对象存储 / CDN | 仅构建阶段 | 不需要 | `/docs/` | 上传 + 配 404 与默认首页 |
| 内网离线 | 可在别处构建 | 可选 | `/docs/` | 只拷产物或改 FROM |
## 常见问题
| 现象 | 原因 | 处理 |
| --- | --- | --- |
| 页面白屏、无样式 | `/docs/` 前缀未剥离,资源 404 | 用 `alias`,或产物放 `docs/` 子目录 |
| 访问 `/` 404 | `base: '/docs/'` | 访问 `/docs/`;容器版已自动 302 |
| 访问 `/docs`(无末尾斜杠)404 | 未配置重定向 | 加 `location = /docs { return 301 /docs/; }` |
| 正文页 404、首页正常 | 缺 `$uri.html` 映射 | `try_files` 补 `$uri.html $uri/index.html` |
| `npm ci` 报 lock 不一致 | Node 版本过低 | 升级到 Node 22 |
| 构建报内存不足 | 容器内存限制过小 | 给到 ≥ 1 GB |
| 端口 5173 被占用 | 已有 dev 进程在跑 | `--port 5174` 或结束旧进程 |
| 反向代理后白屏 | `proxy_pass` 带了末尾斜杠 | 去掉末尾斜杠 |
## 版本固定
开源仓按**固定版本**发布(无 git 历史,单次初始提交)。部署时请记录并锁定 commit:
```bash
SHA=$(git rev-parse HEAD)
echo "部署基线: $SHA" >> deploy.log
docker build -t iotlcm-docs:$SHA .
```
升级文档时,从上游选定基线重新发布后,按新 sha 重建镜像并滚动替换容器:
```bash
docker rm -f iotlcm-docs
docker run -d --name iotlcm-docs --restart unless-stopped -p 8019:80 iotlcm-docs:$NEW_SHA
```
## 相关文档
- [使用手册](/manual/) —— Web 管理端与小程序端操作说明
- [OpenAPI 接口文档](/openapi/) —— V3 接口鉴权与调用说明