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 的流程如下:
- 分别编译 client 和 server
- 最终产出物保留 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"]