pnpm Package Manager Guide
pnpm is a fast, disk space efficient package manager for Node.js projects, used as an alternative to npm or yarn at Quatico.
Why pnpm?
- Very fast installation - optimized dependency resolution
- Disk space efficient - uses symlinks to share dependencies
- Strict dependency management - prevents "phantom dependencies" and workspace leakage
- Active development - modern, actively maintained tool
- Better for monorepos - excellent workspace support
Installation
Using Corepack (Recommended)
Corepack is included with Node.js and is the recommended way to install pnpm:
npm i -g corepack
corepack enable
Standalone Script
Alternatively, use the standalone installation script:
curl -fsSL https://get.pnpm.io/install.sh | sh -
Essential Commands
Managing Dependencies
| Command | Description |
|---|---|
pnpm install | Install all dependencies |
pnpm add <package> | Add a dependency |
pnpm add -D <package> | Add a dev dependency |
pnpm remove <package> | Remove a dependency |
pnpm upgrade [package] | Upgrade dependencies (respects package.json ranges) |
pnpm upgrade --latest | Upgrade to latest versions (updates package.json) |
pnpm why <package> | Show why a package is installed |
Running Scripts
pnpm distinguishes between run and exec:
pnpm run <script>- Runs scripts defined inpackage.jsonpnpm exec <command>- Executes binaries from installed packagespnpm <command>- Tries script first, then binary (convenience)
Recommendation: In scripts and CI/CD, use explicit run or exec.
Monorepo Commands
For monorepos (workspaces):
pnpm -r <command>- Run command recursively in all packagespnpm -F <package>- Filter to specific package(s)pnpm -F ./frontend run build- Run build in frontend packagepnpm --filter "...[origin/develop]" test- Run tests only in changed packages
Project Configuration
package.json
Add to your package.json to enforce pnpm usage:
{
"packageManager": "pnpm@9.1.2",
"scripts": {
"preinstall": "npx only-allow pnpm"
}
}
Don't add preinstall script to published packages - only use in private projects!
.npmrc Configuration
Common pnpm-specific settings:
# Allow peer dependency version mismatches (e.g., for eslint)
pnpm.peerDependencyRules.allowAny[]=eslint
# Hoist specific packages (if needed for compatibility)
hoist-pattern[]=@types/*
hoist-pattern[]=*babel*
Monorepo Workspaces
Setup
Create a pnpm-workspace.yaml file:
packages:
- 'packages/*'
- 'apps/*'
Workspace Dependencies
Use workspace:* protocol for internal dependencies:
{
"dependencies": {
"@my/package": "workspace:*"
}
}
This automatically links workspace packages during development and resolves to proper versions when published.
Migration from yarn/npm
Step-by-Step Migration
-
Create workspace config (if monorepo):
# pnpm-workspace.yaml
packages:
- 'packages/*' -
Import lockfile:
pnpm import yarn.lock && rm yarn.lock
# Or for npm
pnpm import package-lock.json && rm package-lock.json -
Update package.json scripts:
yarn clean→pnpm cleanyarn --cwd ../path clean→pnpm --dir ../path run cleanyarn --cwd ../path clean→pnpm -F '@my/package' clean(in monorepo)
-
Enable pnpm:
corepack use pnpm@latest -
Install and normalize:
pnpm install -
Test your build:
pnpm run build
Troubleshooting
If you encounter issues with the symlinked node_modules structure:
-
Try hoisted mode (temporary, for compatibility):
# .npmrc
node-linker=hoistedThen reinstall:
rm -rf node_modules && pnpm install -
Fix specific packages - hoist only what's needed:
hoist-pattern[]=@types/* -
Add missing dependencies - some packages may need explicit dependencies that were previously available through hoisting
Docker Integration
Use corepack in Dockerfiles:
RUN npm i -g corepack@latest && pnpm -v
The pnpm -v call ensures pnpm is downloaded during build, preventing delays at runtime.
Optimizing Docker Layers
Use pnpm fetch to cache dependencies:
COPY pnpm-lock.yaml .npmrc package.json ./
RUN pnpm fetch
COPY . .
RUN pnpm install --offline
This keeps dependency cache separate from source code changes.
Best Practices
- Use corepack for consistent pnpm versions across team and CI
- Commit
pnpm-lock.yamlto version control - Use
workspace:*for internal monorepo dependencies - Set
packageManagerfield in package.json - Use explicit
run/execin scripts and CI/CD - Avoid
node-linker=hoistedunless necessary for compatibility