5.3 KiB
name, description
| name | description |
|---|---|
| pnpm-store | Content-addressable storage system that makes pnpm fast and disk-efficient |
pnpm Store
pnpm uses a content-addressable store to save disk space and speed up installations. All packages are stored once globally and hard-linked to project node_modules.
How It Works
- Global Store: Packages are downloaded once to a central store
- Hard Links: Projects link to store instead of copying files
- Content-Addressable: Files are stored by content hash, deduplicating identical files
Storage Layout
<store-dir>/ # Global content-addressable store (pnpm store path)
└── files/
└── <hash>/ # Files stored by content hash
project/
└── node_modules/
├── .pnpm/ # Virtual store (hard links to global store)
│ ├── lodash@4.17.21/
│ │ └── node_modules/
│ │ └── lodash/
│ └── express@4.18.2/
│ └── node_modules/
│ ├── express/
│ └── <deps>/ # Flat structure for dependencies
├── lodash -> .pnpm/lodash@4.17.21/node_modules/lodash
└── express -> .pnpm/express@4.18.2/node_modules/express
Store Commands
# Show store location
pnpm store path
# Remove unreferenced packages
pnpm store prune
# Check store integrity
pnpm store status
# Add package to store without installing
pnpm store add <pkg>
Configuration
Store/linker settings live in pnpm-workspace.yaml (camelCase), not .npmrc.
Store Location
storeDir: ~/.local/share/pnpm/store
The default store path is OS-specific (e.g. ~/.local/share/pnpm/store on Linux, ~/Library/pnpm/store on macOS). Find it with pnpm store path.
Virtual Store
The virtual store (.pnpm in node_modules) contains hard links to the global store:
virtualStoreDir: node_modules/.pnpm
virtualStoreDirMaxLength: 60 # lower this for long-path issues on Windows
nodeLinker: hoisted # alternative flat layout
Disk Space Benefits
pnpm saves significant disk space:
- Deduplication: Same package version stored once across all projects
- Content deduplication: Identical files across different packages stored once
- Hard links: No copying, just linking
Check disk usage
# Compare actual vs apparent size
du -sh node_modules # Apparent size
du -sh --apparent-size node_modules # With hard links counted
Global Virtual Store
With enableGlobalVirtualStore: true, projects skip the per-project node_modules/.pnpm directory entirely; their node_modules contains only symlinks into one shared virtual store at <store-path>/links/, keyed by dependency-graph hash. In pnpm v11 it is the default for pnpm dlx/pnx and global installs; for project installs it is still opt-in. See features-global-virtual-store for details and the git-worktrees multi-agent workflow.
enableGlobalVirtualStore: true
Node Linker Modes
Configure how node_modules is structured (nodeLinker in pnpm-workspace.yaml):
nodeLinker: isolated # default: symlinked virtual store (strict, no phantom deps)
# nodeLinker: hoisted # flat node_modules (npm-like) for tools that dislike symlinks
# nodeLinker: pnp # Plug'n'Play, no node_modules (set `symlink: false` too)
Isolated Mode (Default)
- Strict dependency resolution
- No phantom dependencies
- Packages can only access declared dependencies
Hoisted Mode
- Flat
node_moduleslike npm - For compatibility with tools that don't support symlinks
- Loses strictness benefits
Side Effects Cache
Cache build outputs for native modules (enabled by default):
sideEffectsCache: true
sideEffectsCacheReadonly: false # only read the cache, don't create it
Read-only / Frozen Store
frozenStore: true (v11.7+) lets pnpm install run against a read-only store (Nix store, read-only bind mount, OCI layer). Pair with --offline --frozen-lockfile; the store must already contain everything, including approved build outputs.
pnpm install --frozen-store --offline --frozen-lockfile
Shared Store Across Machines
For CI/CD, you can share the store:
# GitHub Actions example
- uses: pnpm/action-setup@v4
with:
run_install: false
- name: Get pnpm store directory
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
Troubleshooting
Store corruption
# Verify and fix store
pnpm store status
pnpm store prune
Hard link issues (network drives, Docker)
# auto (default) tries clone -> hardlink -> copy
packageImportMethod: copy
Permission issues
# Fix store permissions (find the path with `pnpm store path`)
chmod -R u+w "$(pnpm store path)"