docker/install-pnpm.sh linked only `pnpm` into /usr/local/bin, so the image had no `pn`, `pnpx`, or `pnx`. The script now hardlinks the binary as each alias in /opt/pnpm and symlinks them onto PATH, as the npm package and self-update do for the native binary. Both the v12 binary and the v11 executable read the launch name from the hardlink and run `pnpm dlx` for `pnpx` and `pnx`. The docs recipe for copying /opt/pnpm into another image links the aliases too. The install script fails the build if `pn` does not print the installed version or `pnpx` and `pnx` do not run `pnpm dlx`. Fixes pnpm/pnpm#17275 --------- Co-authored-by: Zoltan Kochan <z@kochan.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
pnpm base Docker image
Official base image for pnpm, published to GitHub Container Registry.
ghcr.io/pnpm/pnpm
Based on debian:stable-slim with the pnpm standalone binary. Node.js is not bundled — install the version you need inside your own image with pnpm runtime set node <version> -g (the -g flag makes node available on PATH for subsequent layers and at runtime).
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. |
<version>-alpine, <major>-alpine, alpine |
The same, based on Alpine Linux with the musl build of pnpm. |
Alpine variant
The -alpine tags are based on alpine:3.24 and contain the musl build of pnpm in /opt/pnpm. They are published for pnpm 12 and later.
To add pnpm to an existing image, such as node:<version>-alpine, copy the whole directory. The dist directory next to the binary holds the bundled node-gyp:
FROM node:24-alpine
COPY --from=ghcr.io/pnpm/pnpm:12-alpine /opt/pnpm /opt/pnpm
RUN for bin in pnpm pn pnpx pnx; do ln -s "/opt/pnpm/$bin" "/usr/local/bin/$bin"; done
Supported platforms
linux/amd64, linux/arm64.
Usage
Install Node.js explicitly with pnpm runtime set:
FROM ghcr.io/pnpm/pnpm:latest
RUN pnpm runtime set node 22 -g
WORKDIR /app
COPY . .
RUN pnpm install --frozen-lockfile
CMD ["node", "index.js"]
Or let pnpm install Node.js from devEngines.runtime in your package.json:
{
"devEngines": {
"runtime": {
"name": "node",
"version": "22.x",
"onFail": "download"
}
}
}
FROM ghcr.io/pnpm/pnpm:latest
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
CMD ["pnpm", "start"]
Build locally
VERSION=11.0.0-rc.2
SHA=$(curl -fsSL "https://github.com/pnpm/pnpm/releases/download/v${VERSION}/pnpm-linux-x64.tar.gz" \
| sha256sum | awk '{print $1}')
docker buildx build \
--build-arg PNPM_VERSION=${VERSION} \
--build-arg PNPM_SHA256_AMD64=${SHA} \
--platform linux/amd64 \
--load \
-t pnpm-test ./docker
docker run --rm pnpm-test pnpm --version
For the Alpine image, checksum pnpm-linux-x64-musl.tar.gz instead and pass -f docker/Dockerfile.alpine.
Release
Images are built and pushed by .github/workflows/docker.yml on release: published, or manually via workflow_dispatch. The build fails if pnpm --version in the image doesn't match the PNPM_VERSION build-arg.