Skip to main content

Write Dockerfile for PNPM Monorepo

首先介绍一下要 Build 的这个 Monorepo 具体是什么样子的,它的目录结构如下:

.
├── Dockerfile
├── LICENSE
├── client
│   ├── package.json
│   ├── src
│   │   ├── index.html
│   │   ├── index.tsx
│   ├── tailwind.config.js
│   └── tsconfig.json
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── server
├── package.json
├── src
│   ├── app.ts
└── tsconfig.json

其中 server 使用 express 来静态托管 client 项目 build 出来的静态文件,即:

app.use('*', express.static('../../client/dist'));

所以最终的服务只需要起 server 一个就行了,client 作为产出物给 server。

整体 deploy 的流程如下:

  1. 分别编译 client 和 server
  2. 最终产出物保留 client 的 dist 目录,server 的 dist 目录和运行时需要的 node_modules

除了编译之外,还需要保证打出来的镜像足够小,使用多阶段构建来实现这点。

base

FROM node:20-alpine as base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
ENV COREPACK_NPM_REGISTRY="https://registry.npmmirror.com"
RUN corepack enable

base 阶段,主要是配置 pnpm 环境,方便后续继承的阶段可以直接用上 pnpm,还设置 corepack 使用的 registry。

prod-deps

FROM base AS prod-deps
COPY . /app
WORKDIR /app/server
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile --filter=server --config.dedupe-peer-dependents=false

这个阶段,顾名思义,就是只安装 server 需要的 node_modules,其他 dev 的一律不安装,可以有效的减少文件体积。

build

FROM base AS build
COPY . /app
WORKDIR /app
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm build:prod

该阶段就需要全量安装所有依赖,并执行 build

app

FROM base
COPY --from=prod-deps /app/node_modules /app/node_modules
COPY --from=prod-deps /app/server/node_modules /app/server/node_modules
COPY --from=build /app/client/dist /app/client/dist
COPY --from=build /app/server/dist /app/server/dist

这一步就是复制 client 的产出物,并复制 server 依赖的 node_modules,由于使用的是 pnpm,它在 server 下的 node_modules 都是链接到 workspace 目录下的 node_modules,所以两个都要复制。

env

WORKDIR /app/server
# set env file
ENV ENV_FILE="./dist/.env.prod"
EXPOSE 3000

ENTRYPOINT ["node", "./dist/index.js"]

由于我在 server 中设置了读取ENV_FILE这个变量值作为 env 地址,所以需要设置一下。将工作目录设置到 server 下,并启动 server 服务。

完整的 dockerfile 文件

FROM node:20-alpine as base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
ENV COREPACK_NPM_REGISTRY="https://registry.npmmirror.com"
RUN corepack enable

FROM base AS prod-deps
COPY . /app
WORKDIR /app/server
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile --filter=server --config.dedupe-peer-dependents=false

FROM base AS build
COPY . /app
WORKDIR /app
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm build:prod


FROM base
COPY --from=prod-deps /app/node_modules /app/node_modules
COPY --from=prod-deps /app/server/node_modules /app/server/node_modules
COPY --from=build /app/client/dist /app/client/dist
COPY --from=build /app/server/dist /app/server/dist

WORKDIR /app/server
# set env file
ENV ENV_FILE="./dist/.env.prod"
EXPOSE 3000

ENTRYPOINT ["node", "./dist/index.js"]