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

13 KiB
Raw Blame History

title, description, outline
title description outline
部署手册 iotLCM 文档站的推荐环境、编译方法与部署方式 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

获取代码

git clone https://git.5iot.com/iotlcm-public/iotlcm-docs.git
cd iotlcm-docs

开源仓不含 git 历史,只有一个初始提交,这是预期行为:

git log --oneline
# 92f239f chore: 初始开源发布

⚠️ 部署前必读:路径前缀

这是本项目最容易出错的地方,部署失败九成是这个原因。

文档站配置里 base: '/docs/'(见 .vitepress/config.mts),因此构建产物中所有资源引用都是绝对路径:

<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。

构建镜像

SHA=$(git rev-parse --short HEAD)
docker build -t iotlcm-docs:$SHA .

用 commit sha 做标签,便于追溯与回滚;不建议只用 latest。

启动容器

docker run -d \
  --name iotlcm-docs \
  --restart unless-stopped \
  -p 8019:80 \
  iotlcm-docs:$SHA

nginx 配置(关键)

docker/nginx.conf 用 alias 把 /docs/ 前缀剥掉:

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 后缀,需要服务器做内部映射。

部署后自检

# 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 转发。

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 中镜像名是占位值,需替换为自己的仓库地址:

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 为外部网络,若不存在需先创建:

docker network create iotlcm-network

启动:

TAG=$(git rev-parse --short HEAD) docker compose up -d

其他环境

方式一:本地开发与预览(改文档时用)

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:

npx vitepress dev . --host 0.0.0.0
# 然后访问 http://<本机IP>:5173/docs/

端口被占用时换端口:

npx vitepress dev . --port 5174

方式二:构建静态产物 + 任意 Web 服务器

这是不使用 Docker 时最通用的做法。

编译

npm ci
npm run build
# 产物在 .vitepress/dist

本地验证构建结果(不要用于生产):

npm run preview
# http://127.0.0.1:4173/docs/

部署到 nginx(子路径挂载,推荐)

把产物放到一个 docs 子目录,即可与 base 天然对齐,无需 alias:

sudo mkdir -p /var/www/iotlcm/www
sudo cp -r .vitepress/dist /var/www/iotlcm/www/docs
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 后重新构建:

sed -i "s#base: '/docs/'#base: '/'#" .vitepress/config.mts
npm run build
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 等:

npm run build
# 上传 .vitepress/dist 全部内容到桶内 docs/ 前缀下

要点:

  • 上传时保持 /docs/ 前缀(或改 base 后重新构建)
  • 配置默认首页为 docs/index.html
  • 配置 404 页面为 docs/404.html(产物已自带)
  • cleanUrls: true 意味着页面 URL 不带 .html,需对象存储支持「无后缀映射到 .html」,否则需关闭 cleanUrls 重新构建

方式三:内网 / 离线环境

机器无法访问公网 npm 源时:

方案 A:使用内网镜像源

npm ci --registry=https://your-internal-registry/

方案 B:在有网机器构建后只拷产物

产物是纯静态文件,不需要在目标机器上装 Node:

# 有网机器
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:

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:

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:

SHA=$(git rev-parse HEAD)
echo "部署基线: $SHA" >> deploy.log
docker build -t iotlcm-docs:$SHA .

升级文档时,从上游选定基线重新发布后,按新 sha 重建镜像并滚动替换容器:

docker rm -f iotlcm-docs
docker run -d --name iotlcm-docs --restart unless-stopped -p 8019:80 iotlcm-docs:$NEW_SHA

相关文档