使用 Docker
注意
在构建期间,不可能在 Docker 容器和主机文件系统之间创建引用链接或硬链接。你可以做的下一个最佳操作是使用 BuildKit 缓存挂载在构建之间共享缓存。或者,你可以使用 podman ,因为它可以在构建期间挂载 Btrfs 卷。如果使用 BuildKit 缓存挂载,请将 pnpm 存储缓存的作用域限制在相互信任的构建之间。可由不受信任的构建写入的存储缓存,不应被受信任的构建复用。
官方 pnpm 基础镜像#
官方的 pnpm 基础镜像已发布到 GitHub 容器注册源,地址为 ghcr.io/pnpm/pnpm。 It is based on debian:stable-slim and contains only the pnpm standalone binary. Node.js is not bundled. 这样你可以自己选择 Node.js 版本(无论是在 Docker 文件中还是运行时),而不是被锁定在基础镜像自带的 Node 版本里。
标签#
| 标签 | 含义 |
|---|---|
<version> |
精确的、不可更改的(例如 11.0.0)。包含预发布版本。 |
<major> |
跟踪该主要版本(例如“11”)中的最新稳定版本。 |
latest |
Highest stable pnpm version. 不更新预发布版本。 |
支持的平台:linux/amd64、linux/arm64。
安装 Node.js#
Let pnpm install Node.js automatically from devEngines.runtime in your package.json:
{
"devEngines": {
"runtime": {
"name": "node",
"version": "22.x",
"onFail": "download"
}
}
}
This example assumes the project has a pnpm-workspace.yaml. If it does not,
use COPY package.json pnpm-lock.yaml ./ for the first copy step.
FROM ghcr.io/pnpm/pnpm:11
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
CMD ["pnpm", "start"]
If your project has a pnpm-workspace.yaml, copy it before running
pnpm install. It contains project settings and, in a workspace, the package
patterns. Omitting it can change how dependencies are installed or make a frozen
install fail. Omit it from the COPY instruction only if your project has no
such file.
何时使用此镜像#
- You want the Node.js version to be pinned by your project via
devEngines.runtimerather than by the base image. - 你希望分别升级 pnpm 和 Node.js。
- 你喜欢一个最小的不带有 Node.js 构建工具链的 Debian 基座。
本页下方的配方都以该镜像为基础,让 pnpm 来安装 Node.js。如果你更喜欢自己的 Node.js 基础镜像,请保留每个配方的其余部分,并改为在该镜像中安装 pnpm。
最小化 Docker 镜像大小和构建时间#
- 使用精简镜像,例如
ghcr.io/pnpm/pnpm或node:XX-slim。 - Use multi-stage if possible and makes sense.
- Use BuildKit cache mounts.
The recipes below use the official pnpm image, which already sets PNPM_HOME=/pnpm and puts /pnpm/bin on PATH, so the default store is at /pnpm/store.
When the base image has a runtime installed with pnpm runtime set node 24 -g,
keep the build cache at a separate location, such as /var/cache/pnpm. Mounting
a cache over /pnpm/store hides the managed runtime for the duration of the
RUN instruction. The examples below set --store-dir to the cache mount.
示例 1:在 Docker 容器中构建包#
由于 devDependencies 仅用于构建捆绑包,因此 pnpm install --prod 将成为独立于 pnpm install 和 pnpm run build
阶段,允许最后阶段仅复制之前阶段的必要文件,最小化最终镜像的尺寸。
node_modules
.git
.gitignore
*.md
dist
FROM ghcr.io/pnpm/pnpm:11 AS base
RUN pnpm runtime set node 24 -g
COPY . /app
WORKDIR /app
FROM base AS prod-deps
RUN --mount=type=cache,id=pnpm,target=/var/cache/pnpm pnpm install --store-dir /var/cache/pnpm --prod --frozen-lockfile
FROM base AS build
RUN --mount=type=cache,id=pnpm,target=/var/cache/pnpm pnpm install --store-dir /var/cache/pnpm --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 映像#
假设你有一个包含 3 个软件包的单存储库:app1、app2 和 common,app1 和 app2 依赖于 common,但彼此不依赖。
你只想保存每个包的必要依赖项, pnpm deploy 可以帮助你仅复制必要的文件和包。
./
├── 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
packages:
- 'packages/*'
syncInjectedDepsAfterScripts:
- build
injectWorkspacePackages: true
node_modules
.git
.gitignore
*.md
dist
FROM ghcr.io/pnpm/pnpm:11 AS base
RUN pnpm runtime set node 24 -g
FROM base AS build
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN --mount=type=cache,id=pnpm,target=/var/cache/pnpm pnpm install --store-dir /var/cache/pnpm --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 缓存才能起作用。
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 lockfile and the project settings, and the layer cache will only be lost when you change the lockfile or the project settings.
pnpm fetch only fills the virtual store. A pnpm install --offline after copying the sources links the dependencies into node_modules. The build stage installs every dependency, and a separate stage installs only the production dependencies, so the final image does not carry the dev dependencies.
This example assumes the project has a pnpm-workspace.yaml. If it does not,
use COPY pnpm-lock.yaml /app/ instead. If you patched any package, also copy
the patches directory before running pnpm fetch.
FROM ghcr.io/pnpm/pnpm:11 AS base
RUN pnpm runtime set node 24 -g
WORKDIR /app
FROM base AS build
COPY pnpm-lock.yaml pnpm-workspace.yaml /app/
RUN pnpm fetch
COPY . /app
RUN pnpm install --offline --frozen-lockfile
RUN pnpm run build
FROM base AS prod-deps
COPY pnpm-lock.yaml pnpm-workspace.yaml /app/
RUN pnpm fetch --prod
COPY . /app
RUN pnpm install --offline --frozen-lockfile --prod
FROM base
COPY package.json /app/
COPY --from=prod-deps /app/node_modules /app/node_modules
COPY --from=build /app/dist /app/dist
EXPOSE 8000
CMD [ "pnpm", "start" ]