// Created At 2026-06-07// P4
// GitHub Actions · Caddy · Docker · CI/CD · Deployment

VitePress 文档站接入已有 Docker 基础设施:子域名部署(扩展篇)

本文记录如何将 VitePress 文档站部署为子域名,并接入已有 docker-compose 管理的动态站点(如 Nuxt 博客),共用同一 Caddy 反向代理。

📚 系列导航

本系列共六篇,覆盖从静态网站到生产级 Docker 部署及服务集成的全流程:

  1. 静态网站自动化部署(静态篇) —— 纯前端资源的自动化发布
  2. 动态网站自动化部署(动态篇) —— 后端服务 + 环境变量 + 数据库迁移
  3. Docker 极简入门(入门篇) —— 用 Docker + GitHub Actions 实现 CI/CD
  4. Docker 生产级部署(进阶篇) —— 多容器编排、健康检查、自动 HTTPS
  5. 自托管 Umami 分析服务与 Nuxt 4 项目集成指南(扩展篇) —— 集成分析服务
  6. VitePress 文档站接入已有 Docker 基础设施(静态站实战) —— 本文

📌 前置说明

本文的部署环境

  • 主站点:Nuxt 构建的个人博客(moongate.top),通过 docker-compose 管理(PostgreSQL + Nuxt + Caddy)
  • 文档站:VitePress 构建的组件库文档,部署到子域名 vue.moongate.top
  • 目标:将文档站容器接入现有的 Caddy 反向代理,复用同一套 Docker 网络和域名基础设施

如果你是从零开始部署(没有现有 Caddy、没有 docker-compose):

  • 本文部分内容(如网络连接)可简化
  • 直接 docker run -p 80:80 即可运行

本文的核心价值在于 VitePress 静态站如何与现有 Docker 基础设施整合,而非从零搭建。

关于静态文件目录:本文沿用系列教程的自定义目录风格(/docs)。如果你习惯使用 Caddy 默认目录(/srv/usr/share/caddy),可相应调整,不影响最终效果。

🎯 适用场景

  • ✅ 已有 docker-compose 管理的动态站点(如 Nuxt 博客),需要将静态文档站作为子域名接入
  • ✅ 希望多个站点共用同一 Caddy 反向代理和 HTTPS 证书
  • ✅ 使用 Docker 部署静态网站,但不熟悉网络配置
  • ✅ 遇到 pnpm 供应链安全检查报错,需要解决方案
  • ✅ 需要将 VitePress 文档站接入自动化部署

📌 版本声明

工具版本说明
Node.js24.x最新 LTS
pnpm10.x高性能包管理器
VitePress1.6.x文档生成器
Docker29.x容器运行时
Caddy2.8+静态文件服务器 + 反向代理
GitHub Actions最新CI/CD 平台

🏗️ 系统架构(接入现有基础设施)

背景:本文档站部署在已有 Nuxt 博客的基础设施之上。主站点 moongate.top 由 Nuxt + PostgreSQL + Caddy 组成,通过 docker-compose 管理。文档站作为子域名 vue.moongate.top 接入同一 Caddy。

架构说明:实际运行中存在两个 Caddy:

  • 网关 Caddy(docker-compose 管理):接收外部 HTTPS 请求,反向代理到内部服务
  • 文档站 Caddymoongate-vue 容器内):仅负责服务静态文件,监听 80 端口

由于两者在同一 Docker 网络中,网关 Caddy 通过 reverse_proxy moongate-vue:80 直接通信,无需暴露端口到宿主机。容器间通信为内网明文 HTTP(80 端口),外层 HTTPS 证书由网关 Caddy 自动托管解析。

┌─────────────┐     ┌─────────────────┐     ┌─────────────────┐
  本地开发    │────▶│  GitHub Actions │────▶│   阿里云 ACR  git push   自动构建镜像   镜像仓库└─────────────┘     └─────────────────┘     └─────────────────┘
┌─────────────────────────────────────────────────────────────────┐
                        阿里云 ECS 服务器  ┌───────────────────────────────────────────────────────────┐              Docker 网络: my-blog_blog-network  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐   Caddy   Nuxt 应用  PostgreSQL  (网关代理) │◀──▶│   (博客)    │◀──▶│   (数据库)  │   │  │
  └──────┬──────┘    └─────────────┘    └─────────────┘  ┌─────────────────────────────────────────┐         └──│           moongate-vue    (VitePress + 内部 Caddy 静态服务)    │   │  │
            └─────────────────────────────────────────┘  └───────────────────────────────────────────────────────────┘└─────────────────────────────────────────────────────────────────┘

🔄 与动态应用的核心差异

维度Nuxt 动态应用VitePress 静态站
构建产物.output 服务端代码纯静态 HTML/CSS/JS
运行环境Node.js 运行时静态文件服务器
基础镜像node:alpinecaddy:alpine
端口映射需要暴露端口内部网络访问,无需端口
数据库PostgreSQL
环境变量多个敏感配置
管理方式docker-compose 编排独立 docker run + 网络连接
Caddy 代理reverse_proxy app:3000reverse_proxy moongate-vue:80

🐳 第一步:Dockerfile 编写

VitePress 是静态站点生成器,构建后输出纯静态文件。因此 Dockerfile 分为两个阶段:

# ==================== 构建阶段 ====================
FROM node:24-alpine AS builder

WORKDIR /app

# 安装 git(VitePress 1.x 需要 git 来获取最后更新时间)
RUN apk add --no-cache git

# 安装 pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate

# 复制依赖文件
COPY package.json pnpm-lock.yaml ./

# ⚠️ 关键点:使用 --ignore-scripts 跳过 pnpm 的供应链安全检查
# 否则会触发 ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION
RUN pnpm install --frozen-lockfile --ignore-scripts

# 复制源代码并构建
COPY . .
RUN pnpm docs:build

# ==================== 运行阶段 ====================
FROM caddy:alpine

# 复制构建产物到自定义目录 /docs
# 注意:需要在 Caddyfile 中通过 root * /docs 指定根目录
COPY --from=builder /app/docs/.vitepress/dist /docs

EXPOSE 80

⚠️ 关键踩坑点

1. Git 依赖

VitePress 1.x 的默认主题会调用 git 命令获取文件的最后更新时间。如果容器中没有 git,构建会报错:

[vitepress] spawn git ENOENT

解决方案:在构建阶段安装 git:

RUN apk add --no-cache git

2. pnpm 供应链安全检查

pnpm 10.x 默认启用供应链安全检查,拦截发布时间 < 24 小时的包:

[ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION] @types/node@25.9.2 was published ... within the minimumReleaseAge cutoff
[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: esbuild@0.21.5, vitepress-theme-demoblock@3.1.3

解决方案:使用 --ignore-scripts 参数跳过检查:

RUN pnpm install --frozen-lockfile --ignore-scripts

3. 文件权限

Caddy 官方镜像默认以 caddy 非 root 用户运行(安全考虑)。如果构建产物权限不正确,容器启动后 Caddy 可能无法读取静态文件,导致 403 Forbidden

解决方案:在 Caddyfile 中确保 root 目录可读,或使用 --chown 参数:

COPY --from=builder --chown=caddy:caddy /app/docs/.vitepress/dist /docs

如果你使用自定义目录 /docs,也可以在 Caddyfile 中正常配置 root * /docs,Caddy 会自动处理权限。

🚀 第二步:GitHub Actions 工作流

name: Deploy Docs To Aliyun

on:
  push:
    branches: [main]
    paths:
      - "docs/**"
      - "package.json"
      - "pnpm-lock.yaml"
      - "Dockerfile"
      - ".github/workflows/deploy-docs.yml"

jobs:
  ci:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Aliyun ACR
        uses: docker/login-action@v3
        with:
          registry: ${{ secrets.ACR_REGISTRY }}
          username: ${{ secrets.ACR_USERNAME }}
          password: ${{ secrets.ACR_PASSWORD }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: Dockerfile
          push: true
          tags: |
            ${{ secrets.ACR_REGISTRY }}/moongate/moongate-vue:latest
            ${{ secrets.ACR_REGISTRY }}/moongate/moongate-vue:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Deploy to Server via SSH
        uses: appleboy/ssh-action@v1.0.0
        env:
          ACR_REGISTRY: ${{ secrets.ACR_REGISTRY }}
          ACR_USERNAME: ${{ secrets.ACR_USERNAME }}
          ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }}
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          envs: ACR_REGISTRY, ACR_USERNAME, ACR_PASSWORD
          script: |
            set -e

            # 登录 ACR
            echo "$ACR_PASSWORD" | docker login "$ACR_REGISTRY" -u "$ACR_USERNAME" --password-stdin

            # 拉取最新镜像
            docker pull $ACR_REGISTRY/moongate/moongate-vue:latest

            # 强制删除旧容器(避免容器挂起导致的死锁)
            docker rm -f moongate-vue || true

            # 运行新容器(连接到博客的 Docker 网络)
            docker run -d \
              --name moongate-vue \
              --restart unless-stopped \
              --network my-blog_blog-network \
              $ACR_REGISTRY/moongate/moongate-vue:latest

            # 清理旧镜像
            docker image prune -f --filter "until=24h"

与动态应用 workflow 的差异

差异点Nuxt 动态应用VitePress 静态站
触发路径全量触发docs/** 变更触发
构建参数需要 NUXT_PUBLIC_SITE_URL无需
部署脚本docker-compose独立 docker run + --network
环境变量多个 Secrets 传递无需
数据库迁移
容器更新docker compose up -ddocker rm -f + docker run

🌐 第三步:服务器端配置

3.1 首次部署:运行容器并连接网络

# 拉取镜像
docker pull crpi-xxx/moongate/moongate-vue:latest

# 运行容器(加入博客的网络)
docker run -d \
  --name moongate-vue \
  --restart unless-stopped \
  --network my-blog_blog-network \
  crpi-xxx/moongate/moongate-vue:latest

注意:不需要 -p 端口映射。Caddy 通过 Docker 内部网络直接访问容器,不经过宿主机端口。

3.2 Caddyfile 配置

/var/www/my-site/Caddyfile 中添加子域名配置,并指定静态文件根目录:

# 组件库文档站
vue.moongate.top {
    reverse_proxy moongate-vue:80
    encode gzip zstd
}

# 可选:添加 www 重定向
www.vue.moongate.top {
    redir https://vue.moongate.top{uri} permanent
}

说明

  • 网关 Caddy 只负责反向代理,不负责静态文件服务
  • 静态文件服务由文档站容器内的 Caddy 负责
  • 容器间通信使用内网 HTTP,HTTPS 证书由网关 Caddy 统一托管

3.3 重启网关 Caddy

cd /var/www/my-site
docker compose restart caddy

3.4 验证网络连接

# 查看容器网络
docker inspect moongate-vue | grep -A5 "Networks"

# 从网关 Caddy 容器测试连接
docker exec my-blog-caddy wget -qO- http://moongate-vue:80 | head -5

🌍 第四步:DNS 配置

在阿里云 DNS 控制台添加 A 记录:

记录类型主机记录记录值
Avue123.56.107.115

等待 DNS 生效后,访问 https://vue.moongate.top 即可看到文档站。

🔧 第五步:完整配置清单

GitHub Secrets

Secret说明
ACR_REGISTRY阿里云 ACR 仓库地址
ACR_USERNAME阿里云用户名
ACR_PASSWORDACR 固定密码
SERVER_HOST服务器 IP
SERVER_USERSSH 用户名
SSH_PRIVATE_KEYSSH 私钥

服务器端命令速查

# 首次部署:运行容器并连接网络
docker run -d \
  --name moongate-vue \
  --restart unless-stopped \
  --network my-blog_blog-network \
  crpi-xxx/moongate/moongate-vue:latest

# 常用运维命令
docker logs moongate-vue          # 查看日志
docker restart moongate-vue       # 重启容器
docker stop moongate-vue          # 停止容器
docker start moongate-vue         # 启动容器
docker rm -f moongate-vue          # 强制删除容器

# 网络管理
docker network connect my-blog_blog-network moongate-vue  # 连接网络
docker network disconnect my-blog_blog-network moongate-vue  # 断开网络

🐛 踩坑记录

问题原因解决方案
spawn git ENOENT容器中没有 gitRUN apk add --no-cache git
ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATIONpnpm 供应链安全检查--ignore-scripts
ERR_PNPM_IGNORED_BUILDS构建脚本被忽略同上
Caddy 返回 403静态文件权限不足使用 --chown=caddy:caddy 或检查目录权限
Caddy 返回 502文档站容器未连接到 Caddy 的网络docker network connect
域名无法访问DNS 未配置添加 A 记录
Caddy 无法解析 moongate-vue 主机名容器不在同一网络使用 --network 参数运行
CI 部署时容器名冲突旧容器未完全清理使用 docker rm -f 强制删除

📈 部署流程图

┌─────────────────────────────────────────────────────────────────────┐
                           开发者本地  $ git add docs/  $ git commit -m "update docs"  $ git push origin main└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
                        GitHub Actions  1. 检出代码  2. 安装 pnpm、依赖  3. 执行 pnpm docs:build  4. 构建 Docker 镜像  5. 推送到阿里云 ACR  6. SSH 到服务器  7. docker pull && docker rm -f && docker run --network ...└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
                          阿里云 ECS  1. 拉取最新镜像  2. 强制删除旧容器  3. 启动新容器(加入博客网络)  4. 网关 Caddy 代理 vue.moongate.top moongate-vue:80└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
                           用户访问                    https://vue.moongate.top└─────────────────────────────────────────────────────────────────────┘

📊 与动态应用部署的优劣对比

维度VitePress 静态站Nuxt 动态应用
部署复杂度⭐ 极低⭐⭐⭐⭐ 较高
构建时间~30 秒~2 分钟
镜像体积~50 MB~200 MB
运行时内存~20 MB~150 MB
启动时间<1 秒~5 秒
端口配置无需暴露端口需要端口映射
网络配置需连接到 Caddy 网络compose 自动管理
容器管理独立 docker rundocker-compose 编排
可维护性极高中等

🎉 总结

VitePress 文档站接入已有 Docker 基础设施的关键步骤:

  1. Dockerfile:使用多阶段构建 + Caddy 静态服务器,注意:
    • git 依赖(VitePress 需要)
    • pnpm 供应链安全检查(--ignore-scripts
    • 文件权限(可选 --chown=caddy:caddy
  2. 镜像推送:推送到阿里云 ACR,利用 GitHub Actions 缓存加速
  3. 容器部署:使用 docker rm -f 强制替换,--network 接入现有网络
  4. Caddy 代理:在网关 Caddyfile 中添加子域名配置,指定 root * /docs
  5. DNS 解析:为子域名添加 A 记录

架构亮点

  • 双 Caddy 架构:网关 Caddy 处理 HTTPS 和路由,容器内 Caddy 仅服务静态文件
  • 内网通信:容器间通过 Docker 内部网络通信,无需暴露端口到宿主机
  • 统一证书管理:HTTPS 证书由网关 Caddy 统一托管,子域名自动继承
  • 系列风格统一:沿用自定义目录 /docs,与之前教程保持一致

与动态应用不同,静态站部署更简单、资源占用更少。本文完整记录了接入已有 Docker 基础设施的全过程,这套流程同样适用于任何静态站点生成器(如 Hexo、Hugo、Astro 等),只需调整构建命令和静态文件输出目录即可。

如果这篇文档对你有帮助,可以请我喝杯咖啡 ☕️
Ali PayWechat Pay
评论区
© 2026 MOONGATE