跳到主要内容
版本:Next

使用 Docker

备注

在构建时无法在 Docker 容器和主机文件系统之间创建 reflinks 或硬链接。 你能做的下一个最好的选择是使用 BuildKit 缓存挂载来在构建之间共享缓存。另外,你也可以使用 podman,因为它可以在构建时挂载 Btrfs 卷。

最小化 Docker 镜像大小和构建时间

  • 使用小型镜像,例如 node:XX-slim
  • 尽可能利用多阶段构建,并确保其合理性。
  • 利用 BuildKit 缓存挂载。

示例 1:在 Docker 容器中构建打包文件

由于 devDependencies 仅在构建打包文件时需要,pnpm install --prod 将作为一个独立的阶段, 与 pnpm installpnpm run build 分开。这样最终阶段只需从早期阶段复制必要的文件, 从而最小化最终镜像的大小。

.dockerignore
node_modules
.git
.gitignore
*.md
dist
Dockerfile
FROM node:20-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
COPY . /app
WORKDIR /app

FROM base AS prod-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile

FROM base AS build
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm run build

FROM base
COPY --from=prod-deps /app/node_modules /app/node_modules
COPY --from=build /app/dist /app/dist
EXPOSE 8000
CMD [ "pnpm", "start" ]

示例 2:在 monorepo 中构建多个 Docker 镜像

假设你有一个包含 3 个包的 monorepo:app1、app2 和 common;app1 和 app2 依赖于 common,但它们之间互不依赖。

你想为每个包只保存必要的依赖项,pnpm deploy 可以帮助你只复制必要的文件和包。

monorepo 的结构
./
├── Dockerfile
├── .dockerignore
├── .gitignore
├── packages/
│ ├── app1/
│ │ ├── dist/
│ │ ├── package.json
│ │ ├── src/
│ │ └── tsconfig.json
│ ├── app2/
│ │ ├── dist/
│ │ ├── package.json
│ │ ├── src/
│ │ └── tsconfig.json
│ └── common/
│ ├── dist/
│ ├── package.json
│ ├── src/
│ └── tsconfig.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── tsconfig.json
pnpm-workspace.yaml
packages:
- 'packages/*'
syncInjectedDepsAfterScripts:
- build
injectWorkspacePackages: true
.dockerignore
node_modules
.git
.gitignore
*.md
dist
Dockerfile
FROM node:20-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

FROM base AS build
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm run -r build
RUN pnpm deploy --filter=app1 --prod /prod/app1
RUN pnpm deploy --filter=app2 --prod /prod/app2

FROM base AS app1
COPY --from=build /prod/app1 /prod/app1
WORKDIR /prod/app1
EXPOSE 8000
CMD [ "pnpm", "start" ]

FROM base AS app2
COPY --from=build /prod/app2 /prod/app2
WORKDIR /prod/app2
EXPOSE 8001
CMD [ "pnpm", "start" ]

运行以下命令来构建 app1 和 app2 的镜像:

docker build . --target app1 --tag app1:latest
docker build . --target app2 --tag app2:latest

示例 3:在 CI/CD 中构建

在 CI 或 CD 环境中,BuildKit 缓存挂载可能无法使用,因为 VM 或容器是临时的,只有普通的 docker 缓存才能工作。

因此,一个替代方案是使用带有增量构建层的典型 Dockerfile。对于这种场景,pnpm fetch 是最佳选择,因为它只需要 pnpm-lock.yaml 文件,并且只有在更改依赖项时才会丢失层缓存。

Dockerfile
FROM node:20-slim AS base

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable

FROM base AS prod

COPY pnpm-lock.yaml /app
WORKDIR /app
RUN pnpm fetch --prod

COPY . /app
RUN pnpm run build

FROM base
COPY --from=prod /app/node_modules /app/node_modules
COPY --from=prod /app/dist /app/dist
EXPOSE 8000
CMD [ "pnpm", "start" ]