image(pnpm): add pnpm image with pnpm-managed Node.js - #405
Draft
nozaq wants to merge 1 commit into
Draft
Conversation
pnpm 12 manages Node.js itself: `pnpm runtime set node <ver> -g` installs a global runtime, and its project-aware shims make a bare `node` inside a project run the version that project pins through `devEngines.runtime`. That makes pnpm a version manager in its own right, so it gets its own image alongside `node` rather than being layered onto it, in the shape the `mise`, `rustup`, and `uv` images already use: the tool is in the image, the runtime comes from the project. pnpm is installed the way pnpm's own `ghcr.io/pnpm/pnpm` image installs it — the release archive unpacked into /opt/pnpm with a /usr/local/bin/pnpm symlink — rather than through `pnpm setup`, which is a per-user installer and needs a writable PNPM_HOME. PNPM_HOME stays with the user at its default location, as pnpm refuses a global install when it is not writable. pnpm publishes no detached signature and no checksum on a host separate from the release, so the archive is verified against its build provenance attestation instead: `gh attestation verify` checks that the digest was signed by pnpm's release workflow at the tag being installed, against a Sigstore trusted root committed here. `gh` comes from GitHub's apt repository in the throwaway builder stage, since Debian's own package predates the command. The workflows pass a token to the build as a secret because the attestation is fetched from the GitHub API, where anonymous requests are capped per IP.
Member
Author
|
The three things the description lists as unproven have now run on
The smoke test also ran inside the built image, which exercises The one judgement call still worth a reviewer's eye is the omitted Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
pnpm 12 (stable since v12.0.0) manages Node.js itself.
pnpm runtime set node <ver> -ginstalls a global runtime, and its project-aware shims make a barenodeinside a project run the version that project pins throughdevEngines.runtime(onFail: downloadfetches it on first use). That makes pnpm a version manager in its own right, so it gets its own image alongsidenoderather than being layered onto it — the shapemise,rustup, anduvalready use: the tool is in the image, the runtime comes from the project.Two variants,
12-trixieand12-bookworm. pnpm 11 is not built: it haspnpm runtimebut no project-aware shims, which is the feature this image exists for.Layout
pnpm is installed the way pnpm's own
ghcr.io/pnpm/pnpmimage installs it: the release archive unpacked into/opt/pnpm(thepnpmbinary withdist/next to it, which is where pnpm resolves node-gyp from), with a/usr/local/bin/pnpmsymlink and a version assertion.pnpm setupis deliberately not used. It is a per-user installer, and pointing it at a root-owned directory leaves aPNPM_HOMEthat global installs cannot write to (ERR_PNPM_PNPM_DIR_NOT_WRITABLE).PNPM_HOMEis therefore the user's own~/.local/share/pnpm, pnpm's default on Linux, with itsbin/onPATH— pnpm refuses a global install when it is not.Supply chain
pnpm publishes no detached signature and no checksum on a host separate from the release, so a downloaded checksum would only prove the release host served its own artifact consistently. The archive is verified against its build provenance attestation instead:
gh attestation verifychecks that the digest was signed by pnpm's release workflow (pnpm/pnpm/.github/workflows/release.yml) at the tag being installed, against a Sigstore trusted root committed to this repository and kept in sync byupdate-material.yml.ghis installed in the throwaway builder stage from GitHub's apt repository, with the keyring committed as trust material; Debian's owngh(2.46) predates theattestationcommand. Its SHA256 and both fingerprints match the values published incli/cli'sdocs/install_linux.md.--deny-self-hosted-runnersis not passed. pnpm builds its release binaries on Blacksmith runners, which the OIDC claim reports as self-hosted, so the flag would reject every pnpm release rather than catch anything. Worth a reviewer's eye.Workflow change
The attestation bundle is fetched from the GitHub API, where anonymous requests are capped at 60 per hour per IP, so
build-checks.ymlandpublish-image.ymlpassGITHUB_TOKENto the build as a BuildKit secret. It is mounted for one instruction and never reaches a layer, the SBOM, or the provenance. A build without the secret still works.Verification
Run here:
hadolint --config .hadolint.yamlover every Dockerfile, andshellcheck pnpm/smoke-test.sh: both clean.pnpm/smoke-test.shagainst a real pnpm 12.3.1 binary in a scratchHOME: passes. It covers the global runtime install, a project pinning a different major throughdevEngines.runtime(barenodegives v24 inside the project, v22 outside),pnpm addof a locally authored package, andpnpm exec.--source-ref, and a wrong--signer-workfloweach fail it.Not run here: no Docker daemon in this environment, and
cli.github.comand thepnpm/pnpmAPI are blocked from it, so the image itself has never been built. CI is the first real build — the apt step, the attestation fetch, and the Trivy scan ofdist/node_modulesare all unproven until it runs.Follow-up: a Dev Container template for
pnpminbare-devcontainer/templates, which the image README already links to.Generated by Claude Code