Skip to main content
Version: Next

使用 Docker

note

在构建期间,不可能在 Docker 容器与主机文件系统之间创建 reflinks 或硬链接。 你能做的下一个最好选择是使用 BuildKit 缓存挂载在构建之间共享缓存。或者,你可以使用 podman,因为它可以在构建期间挂载 Btrfs 卷。 如果你使用 BuildKit 缓存挂载,请确保 pnpm 存储缓存仅限于相互信任的构建使用。由不受信任的构建写入的存储缓存不应被受信任的构建重用。

🌐 It is impossible to create reflinks or hardlinks between a Docker container and the host filesystem during build time. The next best thing you can do is using BuildKit cache mount to share cache between builds. Alternatively, you may use podman because it can mount Btrfs volumes during build time. If you use BuildKit cache mounts, keep the pnpm store cache scoped to mutually trusted builds. A store cache that can be written by an untrusted build should not be reused by trusted builds.

官方 pnpm 基础镜像

🌐 Official pnpm base image

一个官方的 pnpm 基础镜像已发布到 GitHub 容器注册表,名称为 ghcr.io/pnpm/pnpm。它基于 debian:stable-slim 并且仅包含 pnpm [独立二进制文件] —— 不包含 Node.js。这样你可以自行选择 Node.js 版本(在你的 Dockerfile 中或运行时),而不是被基础镜像自带的 Node 版本所限制。

🌐 An official pnpm base image is published to GitHub Container Registry as ghcr.io/pnpm/pnpm. It is based on debian:stable-slim and contains only the pnpm standalone binary — Node.js is not bundled. This lets you pick the Node.js version yourself (inside your Dockerfile or at runtime) instead of being locked to whatever Node version a base image ships with.

标签

🌐 Tags

标签含义
<version>精确的,不可变的(例如 11.0.0)。包括预发行版本。
<major>跟踪该主版本内的最新稳定版本(例如 11)。
latest最近的稳定 pnpm 版本。不包括预发行版本更新。

支持的平台:linux/amd64linux/arm64

🌐 Supported platforms: linux/amd64, linux/arm64.

安装 Node.js

🌐 Installing Node.js

使用 pnpm runtime set 并加上全局标志,以便 node 二进制文件在后续层和运行时能够在 PATH 上被发现:

🌐 Use pnpm runtime set with the global flag so the node binary is discoverable on PATH in subsequent layers and at runtime:

FROM ghcr.io/pnpm/pnpm:11
RUN pnpm runtime set node 22 -g
WORKDIR /app
COPY . .
RUN pnpm install --frozen-lockfile
CMD ["node", "index.js"]

或者让 pnpm 从 devEngines.runtime 在你的 package.json 中自动安装 Node.js:

🌐 Or let pnpm install Node.js automatically from devEngines.runtime in your package.json:

package.json
{
"devEngines": {
"runtime": {
"name": "node",
"version": "22.x",
"onFail": "download"
}
}
}
FROM ghcr.io/pnpm/pnpm:11
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
CMD ["pnpm", "start"]

何时使用此图片

🌐 When to use this image

  • You want the Node.js version to be pinned by your project (via pnpm runtime set or devEngines.runtime) rather than by the base image.

  • You want to upgrade pnpm and Node.js independently.

  • You prefer a minimal Debian base without the Node.js build toolchain.

如果你已经有一个首选的 Node.js 基础镜像(例如 node:XX-slim),本页下面的示例仍然是一个不错的选择。

🌐 If you already have a preferred Node.js base image (e.g. node:XX-slim), the recipes further down this page remain a fine choice.

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

🌐 Minimizing Docker image size and build time

  • 使用小图片,例如 node:XX-slim
  • 如果可能的话,利用多阶段并且有意义。
  • 利用 BuildKit 缓存安装。

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

🌐 Example 1: Build a bundle in a Docker container

由于 devDependencies 仅在构建打包包时需要,pnpm install --prod 将与 pnpm installpnpm run build 分为不同的阶段,从而使最终阶段仅从早期阶段复制必要的文件,最小化最终镜像的大小。

🌐 Since devDependencies is only necessary for building the bundle, pnpm install --prod will be a separate stage from pnpm install and pnpm run build, allowing the final stage to copy only necessary files from the earlier stages, minimizing the size of the final image.

.dockerignore
node_modules
.git
.gitignore
*.md
dist
Dockerfile
FROM node:24-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME/bin:$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:在单一代码仓库中构建多个 Docker 镜像

🌐 Example 2: Build multiple Docker images in a monorepo

假设你有一个包含 3 个包的 monorepo:app1、app2 和 common;app1 和 app2 都依赖 common,但彼此不依赖。

🌐 Assuming you have a monorepo with 3 packages: app1, app2, and common; app1 and app2 depend on common but not each other.

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

🌐 You want to save only necessary dependencies for each package, pnpm deploy should help you with copying only necessary files and packages.

Structure of the 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:24-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME/bin:$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 构建镜像:

🌐 Run the following commands to build images for app1 and app2:

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

示例 3:构建在 CI/CD 之上

🌐 Example 3: Build on CI/CD

在 CI 或 CD 环境中,BuildKit 缓存挂载可能不可用,因为 VM 或容器是短暂的,只有正常的 docker 缓存才能工作。

🌐 On CI or CD environments, the BuildKit cache mounts might not be available, because the VM or container is ephemeral and only normal docker cache will work.

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

🌐 So an alternative is to use a typical Dockerfile with layers that are built incrementally, for this scenario, pnpm fetch is the best option, as it only needs the pnpm-lock.yaml file and the layer cache will only be lost when you change the dependencies.

Dockerfile
FROM node:24-slim AS base

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME/bin:$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" ]