图说IT,结构化技术知识,助力年薪百万
侧边栏壁纸
  • 累计撰写 31 篇文章
  • 累计创建 0 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

使用 pnpm workspace 和 standalone 模式构建 Next.js 的 Docker 镜像.md

图说it
2024-08-29 / 0 评论 / 0 点赞 / 152 阅读 / 1,098 字

引言

本文将探讨如何利用 pnpm workspace 和 standalone 模式来构建 Next.js 应用程序的轻量级 Docker 镜像。这种方法通过仅在 node_modules 目录中包含必要的文件,显著减少了最终 Docker 镜像的大小。
在这里插入图片描述

Standalone 模式简介

通常情况下,所有在 dependencies 中列出的包都会被放置在 node_modules 目录中,这会导致镜像体积增大。而在 standalone 模式下构建时,可以从 node_modules 中仅复制必需的文件到一个特定的目录。这样一来,node_modules 将只包含应用程序运行所需的文件,从而大幅减少镜像的大小!

要启用 standalone 模式,请在 next.config.js 文件中添加如下配置:

module.exports = {
  output: 'standalone',
};

目录结构

我们采用了类似于 Turborepo 结构的目录布局:

.
├── apps
│   └── my-app
│       ├── package.json
│       └── ...
├── Dockerfile
├── packages
│   └── other-app
│       └── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── Dockerfile
└── .npmrc

apps 目录包含了作为服务器启动的应用程序,而 packages 目录则包含了 apps 中使用的共享包。

Dockerfile 概览

Dockerfile 设计用于维护每个应用程序的独立性,并将其放置在 /apps/my-app 目录下。下面是 Dockerfile 的概览:

多阶段构建

本 Dockerfile 采用了多阶段构建的方法来分离关注点并最小化最终镜像的大小。

第一阶段:基础环境(base)

FROM node:20.12.0-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"

# 启用 pnpm
RUN corepack enable

这一阶段设置了 pnpm 的环境。

第二阶段:依赖安装(deps)

FROM base AS deps

WORKDIR /app

# 复制 `pnpm install` 所需的文件
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY ./apps/my-app/package.json /app/apps/my-app/package.json
COPY ./packages/ /app/packages/

RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
    pnpm install --frozen-lockfile

这一阶段安装了所需的依赖项。

第三阶段:构建器(builder)

FROM base AS builder

WORKDIR /app

# 复制已安装的 `node_modules` 从 deps 阶段
COPY --from=deps /app/node_modules ./node_modules
# 复制构建目标的目录
COPY ./apps/my-app ./apps/my-app
COPY --from=deps /app/packages ./packages
COPY --from=deps /app/.npmrc ./

# 展开符号链接并复制到 pruned 目录
RUN pnpm --filter=@monorepo/my-app deploy /pruned

WORKDIR /pruned

# 构建应用程序
RUN pnpm --filter=@monorepo/my-app build

这一阶段构建了应用程序并准备部署。

第四阶段:运行器(runner)

FROM base AS runner

WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

# 复制已构建的文件从 builder 阶段
COPY --from=builder /app/apps/my-app/public ./public
COPY --from=builder /pruned/.next/standalone .
COPY --from=builder /pruned/.next/static ./.next/static

CMD ["node", "server.js"]

这一阶段是启动应用程序的实际阶段。

构建 Docker 镜像

使用以下命令来构建 Docker 镜像并检查其大小:

$ docker build -f apps/my-app/Dockerfile --no-cache --target runner --tag myapp:latest --progress=plain

构建后检查镜像:

$ docker images

您将看到一个准备就绪且体积轻量的镜像。

结论

尽管在过程中遇到了一些挑战,比如理解 pnpm deploy 命令及管理多个应用程序在单一 workspace 中的过程,但最终还是成功地构建了一个高效且轻量的 Docker 镜像。

遇到的问题

  • 管理多个应用程序的复杂性:尝试在一个 Dockerfile 中处理所有应用程序目录下的应用,导致 --build-arg 参数增加,使得 Dockerfile 变得繁琐。
    • 解决方案:通过在每个 apps 目录中配置 Dockerfile 来避免这种情况。这样还可以针对每个应用程序单独进行 Node.js 的更新等操作。
  • pnpm deploy -> build 流程的理解不足:对 pnpm 通过符号链接工作的方式以及在 deploy 后将实际文件复制的行为理解不够深入,导致应用程序启动失败。
    • 解决方案:将 docker build--target 参数更改为 builder,然后通过 docker run -it my-app /bin/bash 进入容器并查看目录状态来进行调试。

参考链接

0
博主关闭了所有页面的评论