使用 Docker
在构建期间,不可能在 Docker 容器和主机文件系统之间创建引用链接或硬链接。 你可以做的下一个最佳操作是使用 BuildKit 缓存挂载在构建之间共享缓存。 或者,你可以使用 podman ,因为它可以在构建期间挂载 Btrfs 卷。 如果使用 BuildKit 缓存挂载,请将 pnpm 存储缓存的作用域限制在相互信任的构建之间。 可由不受信任的构建写入的存储缓存,不应被受信任的构建复用。
官方 pnpm 基础镜像
官方的 pnpm 基础镜像已发布到 GitHub 容器注册源,地址为 ghcr.io/pnpm/pnpm。 它基于 debian:stable-slim,并且只包含 pnpm [独立二进制文件] — Node.js 未捆绑。 这样你可以自己选择 Node.js 版本(无论是在 Docker 文件中还是运行时),而不是被锁定在基础镜像自带的 Node 版本里。
标签
| 标签 | 含义 |
|---|---|
<version> | 精确的、不可更改的(例如 11.0.0)。 包含预发布版本。 |
<major> | 跟踪该主要版本(例如“11”)中的最新稳定版本。 |
latest | pnpm 最新稳定版本。 不更新预发布版本。 |
支持的平台:linux/amd64、linux/arm64。
安装 Node.js
使用带有全局标志的 pnpm runtime set,以便后续层和运行时都能在 PATH 中找到 node 二进制文件:
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 从 package.json 中的 devEngines.runtime 自动安装 Node.js:
{
"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"]
何时使用此镜像
- 你希望 Node.js 版本由你的项目(通过
pnpm runtime set或devEngines.runtime)固定,而不是由基础镜像固定。 - 你希望分别升级 pnpm 和 Node.js。
- 你喜欢一个最小的不带有 Node.js 构建工具链的 Debian 基座。
The recipes further down this page start from this image and let pnpm install Node.js. If you prefer your own Node.js base image, keep the rest of each recipe and install pnpm into that image instead.
最小化 Docker 镜像大小和构建时间
- Use a small image, e.g.
ghcr.io/pnpm/pnpmornode:XX-slim. - 如果可能的话,利用多阶段是有意义的。
- 利用 BuildKit 缓存挂载功能。
The recipes below use the official pnpm image, which already sets PNPM_HOME=/pnpm and puts /pnpm/bin on PATH, so the store the cache mounts target is at /pnpm/store.
示例 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 pnpm install --prod --frozen-lockfile
FROM base AS build
RUN pnpm install --frozen-lockfile
RUN pnpm run build
FROM base
COPY /app/node_modules /app/node_modules
COPY /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 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 /prod/app1 /prod/app1
WORKDIR /prod/app1
EXPOSE 8000
CMD [ "pnpm", "start" ]
FROM base AS app2
COPY /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 文件,并且只有在更改依赖项时层缓存才会丢失。
FROM ghcr.io/pnpm/pnpm:11 AS base
RUN pnpm runtime set node 24 -g
FROM base AS prod
WORKDIR /app
COPY pnpm-lock.yaml /app
RUN pnpm fetch --prod
COPY . /app
RUN pnpm run build
FROM base
COPY /app/node_modules /app/node_modules
COPY /app/dist /app/dist
EXPOSE 8000
CMD [ "pnpm", "start" ]