主题
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 | 项目总览.md | 1 |
/guides/* | docs/*.md(顶层,不含 PDF-psoc/ 参考资料) | 3 |
/server/* | server-info/*.md(README.md → /server/) | 14 |
/tools/* | tools/*/README.md | 4 |
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.mdlastUpdated 需要构建环境里有 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/docs8. 后续可扩展
| 项 | 说明 |
|---|---|
| 真域名 + HTTPS | 安全组放行 80/443 后,docs.localhost 换成 docs.your.domain,主 Caddy 自动签证书 |
| 自动重建 | 目前手动上传。可加一个 webhook 容器,push 后自动 rebuild |
| 版本化文档 | 同一镜像打多 tag,Caddy 按路径切换 |
| 全文搜索 | 已用 VitePress 本地搜索(search.provider = 'local'),无需外部服务 |