Skip to content

14 — 文档站(VitePress) ​

部署时间:2026-11-08 05:33~05:40 CST VitePress 静态站,构建进镜像,由容器内 Caddy 托管;公网入口走主 Caddy 的 docs.localhost。


1. 关键决策:不把 VitePress 当服务跑 ​

VitePress 是静态生成器,运行时没有任何东西需要活着。所以:

方案评价
vitepress dev/preview 常驻容器❌ 为一个永不变化的站点常驻 Node 进程,白吃 ~150 MB 内存
构建产物 + 静态托管 ✅✅ 运行时只是文件服务器,实测 39 MB
挂进已有 Caddy 的目录也行,但需要额外把产物搬进 Caddy 容器,不如独立镜像干净

最终形态:多阶段 Dockerfile —— 第一阶段 node:22-alpine 构建,第二阶段 caddy:2.11.9-alpine 只装产物 + 自己的 Caddyfile。重建 = docker compose up -d --build。


2. 部署信息 ​

项值
镜像tenodx/docs:1.0.0(96.5 MB)
容器tenodx-docs
监听127.0.0.1:8088
运行内存~39 MB
网络tenodx-net
部署目录(服务器)/opt/stacks/docs/
源码(仓库)docs-site/
公网入口Caddy docs.localhost → tenodx-docs:80
监控Uptime Kuma「Docs site (VitePress)」,探测 http://tenodx-docs/

同样受安全组限制:公网不可达,需走隧道: tunnel.ps1 -Service web 后访问 http://docs.localhost:13080/


3. 内容来自哪里(单一事实源) ​

不手工编辑 docs-site/src/ 里的拷贝内容。 prepare.ps1 从仓库原有位置装配:

站点分区源路径数量
/overview项目总览.md1
/guides/*docs/*.md(顶层,不含 PDF-psoc/ 参考资料)3
/server/*server-info/*.md(README.md → /server/)14
/tools/*tools/*/README.md4

prepare.ps1 还会重新生成 .vitepress/sidebar.mjs(导航 + 侧边栏), 标题从每个文件的第一个 # 标题 自动读取。所以:

新增一篇文档 → 重跑 prepare.ps1 → 重建镜像,导航自动出现,不用手改配置。

⚠️ 为什么 prepare.ps1 是纯 ASCII ​

Windows PowerShell 5.1 会把无 BOM 的 UTF-8 .ps1 当 ANSI 解码。 第一版带中文标签直接语法崩了。现在脚本内只有英文, 中文标题在运行时从 markdown 文件读取(文件系统字符串不受脚本编码影响)。


4. 更新文档的完整流程 ​

powershell
# 1) 改仓库里的源 markdown(server-info/*.md、docs/*.md、tools/*/README.md ...)

# 2) 重新装配内容 + 侧边栏
powershell -ExecutionPolicy Bypass -File .\docs-site\prepare.ps1

# 3) 打包上传(中文文件名必须用 tar+scp,不能逐文件 base64 管道——会被截断)
tar.exe -czf $env:TEMP\docs-site.tar.gz -C .\docs-site .
scp $env:TEMP\docs-site.tar.gz tenodx:/tmp/docs-site.tar.gz

# 4) 服务器上解包重建
ssh tenodx @"
  rm -rf /opt/stacks/docs && mkdir -p /opt/stacks/docs
  tar -xzf /tmp/docs-site.tar.gz -C /opt/stacks/docs
  cd /opt/stacks/docs && docker compose up -d --build
"@

构建约 60~90 秒(npm install 已缓存镜像层,改内容只重跑 vitepress build)。


5. 踩坑记录 ​

坑 1:lastUpdated: true 导致构建失败 ​

build error: [vitepress] spawn git ENOENT
file: /app/src/guides/Mai2TouchREADME.md

lastUpdated 需要构建环境里有 git 和完整的 .git 历史。 我们只把 markdown 拷进镜像(不带 .git),所以必然失败。

处理:lastUpdated: false。不要为了这个功能把 git + 整个历史塞进镜像。

坑 2:prepare.ps1 的中文被 PS 5.1 按 ANSI 解析 ​

见第 3 节。同一类问题在 refresh.ps1 上也踩过一次。

坑 3:大块 base64 走 SSH 管道会被截断 ​

180 KB 的单行 base64 管道传输后 base64: invalid input。 二进制/大文件用 scp,不要用 echo ... | ssh。

坑 4:caddy file-server 子命令无法表达 try_files ​

VitePress 开了 cleanUrls(/server/01-xxx → /server/01-xxx.html), caddy file-server --root 没有 try_files 选项。改用自带 Caddyfile:

try_files {path} {path}.html {path}/index.html /index.html

(无害提示:Caddyfile 的 caddyfile 代码块语言 VitePress 未内置,会 fallback 成纯文本)


6. 验证结果(2026-11-08 05:40) ​

检查结果
构建✅ 镜像 tenodx/docs:1.0.0,23 篇 markdown
容器✅ running + healthy
/(内部 8088)✅ 200,页面标题 TenoDX 文档
/overview(cleanUrls)✅ 200
/overview.html✅ 200
经 Caddy docs.localhost/✅ 200
/overview✅ 200
/server/✅ 200
/server/13-资源服务器(中文 URL)✅ 200
/tools/hub✅ 200
/assets/ 静态资源✅ 200,长缓存头
落地页未受影响✅ 200
监控✅ 已加入(第 12 项)
运行内存39 MB

7. 运维命令 ​

bash
cd /opt/stacks/docs

docker compose ps
docker compose logs --tail 30
docker compose up -d --build        # 更新内容后重建
docker compose restart              # 仅重启(内容没变时没意义)

# 看站点文件
docker exec tenodx-docs ls /site | head

# 改完验证
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8088/

回滚 ​

bash
cd /opt/stacks/caddy && cp Caddyfile.bak.<最新> Caddyfile && docker compose restart caddy
cd /opt/stacks/docs && docker compose down
docker rmi tenodx/docs:1.0.0
rm -rf /opt/stacks/docs

8. 后续可扩展 ​

项说明
真域名 + HTTPS安全组放行 80/443 后,docs.localhost 换成 docs.your.domain,主 Caddy 自动签证书
自动重建目前手动上传。可加一个 webhook 容器,push 后自动 rebuild
版本化文档同一镜像打多 tag,Caddy 按路径切换
全文搜索已用 VitePress 本地搜索(search.provider = 'local'),无需外部服务

TenoDX · 内部文档