Correct the v12 guides from agent-skills.md through lockfile.md against pnpm/crates, and fix the same wrong text on the v11 twins where pnpm11 confirms it. Part of pnpm/tasks#191. Wrong claims (both versions): - configuring: the global auth file is auth.ini, not rc; list the npm_config_* exceptions (workspace dir, userconfig, URL-scoped auth). - config-dependencies: the hook import used an invalid ESM specifier. - docker: Example 3 ran fetch --prod and built without an offline install or pnpm-workspace.yaml. - errors: drop MISMATCHED_RELEASE_CHANNEL, describe INVALID_NODE_VERSION through nodeVersion, PEER_DEP_ISSUES needs strictPeerDependencies. - faq: no lockfile deletion after a readPackage hook, default store order, junctions depend on symlink privilege. - filtering: `!./lib` matches one project; use `!./lib/**`. - git branch lockfiles: every non [A-Za-z0-9_.-] character becomes `!`, the name is lowercased, merging deletes branch lockfiles, "or" patterns. - git-worktrees: remove the nonexistent worktree:new and shell/wt.sh. - global-packages: orphaned dirs are cleaned by add -g (and update -g in v12), not store prune; document that add -g replaces every group that contains the package. - how-peers-are-resolved: current peer directory names. - completion: built-in completion already covers --filter and scripts. - feature-comparison: broken JSR anchor. Wrong claims (v12 only): - cargo: cargo.indexUrl fails the parse, overrides apply to adding only, merge the duplicated Cargo index section into Registries. - global-virtual-store: pnx keeps its own virtual store (dlx part only; the global-install claims are left for a separate decision). - installation: the npm package installs on Node.js 18, only get-pnpm needs 22.13; trim unverified compatibility rows. Missing facts (v12): agent-skills group filtering and naming, catalogs (catalogs.default, pnx, --save-catalog, error codes), cargo recursive add, build-std, resolution keys, completion zsh/pwsh setup, config dependency location and error codes, include error codes, CI opt-outs and pnpm ci, finders on list, filtering settings and --no-fail-if-no-match, global install refusals, trust for npm-registry package managers, Rust shims, overrides link from aliases. Stale history (v12): drop v11/v10 version markers and "since v11" narration in continuous-integration, filtering, finders, git-worktrees, global-packages, global-virtual-store, how-peers-are-resolved, lockfile, completion, configuring. Review follow-up: the Node.js compatibility table keeps the pnpm 10 column; Docker Example 3 builds with all dependencies in a build stage and ships only production dependencies, with the pnpm-workspace.yaml caveat (both versions); catalogs keep the bare-path form; v11 global packages name `pnpm update -g` as cleaning orphans too; the cargo build-std note says when rust-src must be listed. Second review: build-std needs a non-empty list; the Docker fetch layer also depends on the project settings; branch lockfile names keep only ASCII characters; the conflict merge needs both sides to parse; Rust shims and package managers are default globalShims participants; pnpm self-update also cleans orphaned global groups; v12 pnpm login writes tokens to config.yaml; legacyDirFiltering keeps the subtree wording (the v12 mismatch is pnpm/tasks#196); the v12 Docker example copies the files that `include` names before pnpm fetch.
239 lines
8.2 KiB
Markdown
239 lines
8.2 KiB
Markdown
---
|
||
id: docker
|
||
title: Working with Docker
|
||
---
|
||
|
||
:::note
|
||
|
||
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.
|
||
|
||
:::
|
||
|
||
[podman]: ./podman.md
|
||
|
||
## Official pnpm base image
|
||
|
||
An official pnpm base image is published to GitHub Container Registry as [`ghcr.io/pnpm/pnpm`](https://github.com/pnpm/pnpm/pkgs/container/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.
|
||
|
||
[standalone binary]: ./installation.md#using-a-standalone-script
|
||
|
||
### Tags
|
||
|
||
| Tag | Meaning |
|
||
| --------------------- | ----------------------------------------------------------------------- |
|
||
| `<version>` | Exact, immutable (e.g. `11.0.0`). Includes prereleases. |
|
||
| `<major>` | Tracks the latest stable release within that major (e.g. `11`). |
|
||
| `latest` | Highest stable pnpm version. Not updated for prereleases. |
|
||
|
||
Supported platforms: `linux/amd64`, `linux/arm64`.
|
||
|
||
### Installing Node.js
|
||
|
||
Let pnpm install Node.js automatically from [`devEngines.runtime`](./package_json.md#devenginesruntime) in your `package.json`:
|
||
|
||
```json title="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.
|
||
|
||
```dockerfile title="Dockerfile"
|
||
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.
|
||
|
||
### When to use this image
|
||
|
||
- You want the Node.js version to be pinned by your project via `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.
|
||
|
||
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](./installation.md) into that image instead.
|
||
|
||
## Minimizing Docker image size and build time
|
||
|
||
* Use a small image, e.g. `ghcr.io/pnpm/pnpm` or `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.
|
||
|
||
### Example 1: Build a bundle in a Docker container
|
||
|
||
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.
|
||
|
||
```text title=".dockerignore"
|
||
node_modules
|
||
.git
|
||
.gitignore
|
||
*.md
|
||
dist
|
||
```
|
||
|
||
```dockerfile title="Dockerfile"
|
||
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" ]
|
||
```
|
||
|
||
### Example 2: Build multiple Docker images in a monorepo
|
||
|
||
Assuming you have a monorepo with 3 packages: app1, app2, and common; app1 and app2 depend on common but not each other.
|
||
|
||
You want to save only necessary dependencies for each package, `pnpm deploy` should help you with copying only necessary files and packages.
|
||
|
||
```text title="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
|
||
```
|
||
|
||
```yaml title="pnpm-workspace.yaml"
|
||
packages:
|
||
- 'packages/*'
|
||
syncInjectedDepsAfterScripts:
|
||
- build
|
||
injectWorkspacePackages: true
|
||
```
|
||
|
||
```text title=".dockerignore"
|
||
node_modules
|
||
.git
|
||
.gitignore
|
||
*.md
|
||
dist
|
||
```
|
||
|
||
```dockerfile title="Dockerfile"
|
||
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" ]
|
||
```
|
||
|
||
Run the following commands to build images for app1 and app2:
|
||
|
||
```sh
|
||
docker build . --target app1 --tag app1:latest
|
||
docker build . --target app2 --tag app2:latest
|
||
```
|
||
|
||
### Example 3: Build on CI/CD
|
||
|
||
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.
|
||
|
||
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`.
|
||
|
||
```dockerfile title="Dockerfile"
|
||
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" ]
|
||
```
|