MO Gallery 官网部署耗时分析与 Docker 迁移镜像优化

1570 字
8 分钟
MO Gallery 官网部署耗时分析与 Docker 迁移镜像优化

背景#

MO Gallery 官网 mo-gallery-offical 使用 GitHub Actions 通过 SSH 将源码同步到服务器,然后在服务器执行:

Terminal window
docker compose up -d --build

一次部署虽然最终成功,但 GitHub Actions 运行时间达到 8 分 42 秒:

查看本次部署记录

部署流程本身没有卡死,主要问题是 Docker 在服务器上进行了完整的应用构建,并且把只用于数据库迁移的构建阶段也导出成了一个完整镜像。

日志分段#

根据 Actions 日志,各阶段耗时如下:

阶段耗时
Checkout、SSH、源码同步约 6 秒
pnpm install --frozen-lockfile约 68 秒
prisma generate + next build约 191 秒
导出 migrate 镜像约 230 秒
数据库迁移、容器重建和启动约 17 秒

真正需要关注的是下面这段:

[migrate] exporting layers 155.0s
[migrate] unpacking ... 74.5s

仅 migrate 镜像的导出和解包就占用了接近四分钟。

原因分析#

原来的 Compose 配置如下:

services:
migrate:
build:
context: .
target: builder
command: sh -c "node_modules/.bin/prisma migrate deploy"

而 Dockerfile 的 builder 阶段负责完整的前端构建:

FROM base AS builder
COPY . .
RUN pnpm install --frozen-lockfile
RUN pnpm run build

因此,builder 阶段包含了很多数据库迁移根本不需要的内容:

  • 完整的 workspace 依赖
  • 全部源码
  • .next 构建产物
  • Next.js、Milkdown、Tiptap、图片处理等前端依赖
  • Prisma CLI 以及应用的其他开发依赖

但迁移服务实际上只执行:

Terminal window
prisma migrate deploy

它只需要读取 prisma/schema.prisma、prisma/migrations 和 Prisma CLI。让 Compose 直接使用 target: builder,等于先构建完整应用,再把整个构建阶段导出成一个迁移镜像。

方案调整#

将 Dockerfile 拆成三个职责明确的阶段:

builder -> 完整安装依赖并构建 Next.js 应用
migrate -> 只安装 Prisma CLI,执行数据库迁移
runner -> 只运行 Next.js standalone 产物

1. 新增迁移依赖清单#

在 prisma/migrate-package.json 中只声明迁移所需的两个直接依赖:

{
"name": "mo-gallery-official-migrate",
"private": true,
"dependencies": {
"dotenv": "17.4.2",
"prisma": "7.10.0"
}
}

这里使用精确版本,避免迁移镜像在构建时意外升级 Prisma 主版本。升级根项目 Prisma 版本时,需要同步更新这份清单。

2. 新增独立 migrate 阶段#

# ---- 迁移阶段:只保留 Prisma CLI、dotenv 与迁移文件 ----
FROM base AS migrate
ENV NODE_ENV=production
COPY prisma/migrate-package.json ./package.json
RUN npm install --omit=dev --no-package-lock
COPY prisma.config.ts ./
COPY prisma ./prisma
CMD ["node_modules/.bin/prisma", "migrate", "deploy"]

这个阶段不会复制应用源码,也不会复制 .next 和完整 workspace。prisma.config.ts 中使用了:

import 'dotenv/config'
import { defineConfig } from 'prisma/config'

所以迁移镜像需要 dotenv 和 Prisma CLI,其他应用依赖都不需要。

3. Compose 使用 migrate 阶段#

services:
migrate:
build:
context: .
target: migrate
command: ["node_modules/.bin/prisma", "migrate", "deploy"]
environment:
DATABASE_URL: ${DATABASE_URL:?请在 .env 中设置 DATABASE_URL}

应用服务仍然使用默认的 Dockerfile 最终阶段,也就是只包含 Next.js standalone 运行产物:

services:
app:
build: .
depends_on:
migrate:
condition: service_completed_successfully

这样可以保持原有的启动顺序:数据库迁移成功后,应用容器才启动。

为什么这样能减少耗时#

之前的迁移镜像继承了完整 builder 阶段。即使迁移命令只运行几秒,Docker 仍需要:

  1. 导出包含完整依赖和构建产物的镜像层;
  2. 将这些镜像层写入 Docker 镜像存储;
  3. 解包迁移镜像;
  4. 再创建迁移容器。

调整后,migrate 阶段只包含 Prisma 运行所需内容,完整应用构建仍然只服务于 app 镜像,不再被重复作为迁移镜像导出。

本次日志中旧迁移镜像的导出和解包约耗时 230 秒,因此这部分是最明确的优化目标。实际第一次切换到独立阶段后,新的迁移镜像导出和解包约耗时 74 秒,但同时新增了约 68 秒的精简 Prisma 依赖安装;由于两个镜像阶段并行构建,最终部署从约 8 分 42 秒降至约 6 分 36 秒,实际减少约 2 分 06 秒。

迁移依赖清单只复制 prisma/migrate-package.json,后续源码变化不会使 npm install 层失效。只要 Prisma 版本和迁移依赖没有变化,后续部署应能复用这一层,迁移阶段还会比本次更快。

实测结果#

优化前的部署运行时间约为 8 分 42 秒,优化后的本次运行时间约为 6 分 36 秒,减少约 2 分 06 秒。

查看优化后的部署记录

本次运行的迁移阶段日志如下:

npm install --omit=dev --no-package-lock 约 68 秒
migrate 镜像导出与解包 约 74 秒

因此不能简单地把原来的 230 秒全部视为净收益:精简镜像仍然需要首次安装 Prisma CLI 及其传递依赖。不过迁移镜像已经从“完整应用构建镜像”变成了独立小阶段,后续部署可以复用稳定的 npm 安装层。

当前剩余耗时#

拆分迁移镜像后,部署仍会有两个主要耗时来源:

  1. pnpm install --frozen-lockfile:最新运行中应用构建阶段用了约 108 秒。当前 Dockerfile 为了解决 workspace 包复制顺序问题,将完整源码复制后再安装依赖,普通源码变化会导致依赖安装层失效。
  2. next build:最新运行中用了约 233 秒,服务器需要重新执行 Turbopack 编译和 TypeScript 检查。

后续还可以继续优化:

  • 使用 BuildKit cache mount 持久化 pnpm store;
  • 将根目录和 workspace 的 package.json 与源码分层复制,尽可能复用依赖安装层;
  • 使用更轻量的构建缓存策略;
  • 评估在 CI 构建镜像后推送到镜像仓库,服务器只拉取并重启,减少服务器本地构建时间。

不过这些优化涉及构建缓存和发布链路调整,而独立 migrate 阶段不改变现有发布流程,优先级更高、风险更低。

结论#

部署耗时长并不是 SSH 同步慢,也不是 Prisma 迁移本身慢,而是数据库迁移服务错误地复用了完整应用构建阶段:

target: builder

将其调整为:

target: migrate

并为迁移服务提供只包含 Prisma CLI、dotenv 和迁移文件的独立镜像,可以避免导出完整 builder 镜像,同时保持“迁移成功后再启动应用”的部署语义不变。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

MO Gallery 官网部署耗时分析与 Docker 迁移镜像优化
https://shaio.top/posts/03mogallery/261001官网部署耗时与docker迁移镜像优化/
作者
沙海
发布于
2026-10-01
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
沙海
Hello, I'm Shai.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
84
分类
15
标签
48
总字数
67,035
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
GitHub Actions
博客版本
Firefly v6.12.2
文章许可
CC BY-NC-SA 4.0

文章目录