콘텐츠로 이동

pnpm

pnpm (Performant npm) is a package manager for Node.js that saves disk space by storing all packages in a global content-addressable store and using hard links to project node_modules. This means packages are never duplicated on disk, installs are faster, and all projects share the same cached packages.

Installation

# npm (recommended for bootstrapping)
npm install -g pnpm

# Corepack (built into Node.js 16.9+)
corepack enable
corepack prepare pnpm@latest --activate

# macOS (Homebrew)
brew install pnpm

# Windows (Scoop)
scoop install nodejs-lts
corepack enable

# Linux standalone install
curl -fsSL https://get.pnpm.io/install.sh | sh -

# Upgrade pnpm
pnpm self-update

# Check version
pnpm --version

# Set Node.js version to use (pnpm manages Node versions too)
pnpm env use --global lts
pnpm env use --global 20
# Enable corepack (ships with Node.js)
corepack enable pnpm

# Pin pnpm version per project in package.json
# Add to package.json:
# "packageManager": "pnpm@9.1.0"

Configuration

pnpm uses .npmrc for configuration and pnpm-workspace.yaml for monorepos:

# .npmrc
# Store location (default: ~/.pnpm-store)
store-dir=~/.pnpm-store

# Use shamefully-hoist to fix compatibility with tools that expect flat node_modules
# (only use if a package explicitly requires it)
shamefully-hoist=false

# Strict mode — fail on peer dependency issues
strict-peer-dependencies=true

# Auto-install peer dependencies
auto-install-peers=true

# Save exact versions (no ^ or ~)
save-exact=false

# Custom registry
registry=https://registry.npmjs.org/

# Scoped registry
@myorg:registry=https://npm.myorg.com/

# Link workspace packages by default
link-workspace-packages=true

# Prefer frozen lockfile in CI (set via env var instead)
# PNPM_FROZEN_LOCKFILE=true

Monorepo Workspace Config

# pnpm-workspace.yaml
packages:
  - "packages/*"
  - "apps/*"
  - "!**/__tests__/**"

package.json Overrides

{
  "pnpm": {
    "overrides": {
      "lodash": "^4.17.21",
      "minimist@<1.2.6": "^1.2.6"
    },
    "peerDependencyRules": {
      "ignoreMissing": ["react"],
      "allowAny": ["typescript"]
    },
    "packageExtensions": {
      "some-lib@*": {
        "peerDependencies": {
          "react": "*"
        }
      }
    }
  }
}

Core Commands

CommandDescription
pnpm installInstall all dependencies
pnpm install --frozen-lockfileInstall without updating lockfile (CI)
pnpm add packageAdd a dependency
pnpm add -D packageAdd a dev dependency
pnpm add -g packageInstall globally
pnpm add -O packageAdd as optional dependency
pnpm add package@2.0.0Add specific version
pnpm remove packageRemove a dependency
pnpm updateUpdate packages per semver range
pnpm update --latestUpdate to latest versions
pnpm update packageUpdate a specific package
pnpm outdatedCheck for outdated packages
pnpm run scriptRun a script from package.json
pnpm run -r scriptRun script in all workspace packages
pnpm exec commandRun a command from node_modules/.bin
pnpm dlx packageExecute package without installing (npx equivalent)
pnpm listList installed packages
pnpm list --depth 0List top-level packages only
pnpm why packageShow why a package is installed
pnpm store statusCheck if store is up to date
pnpm store pruneRemove unused packages from store
pnpm auditAudit packages for vulnerabilities
pnpm patch packagePatch a dependency
pnpm deploy ./deploy-dirCreate a production-ready copy
pnpm initInitialize a new package.json
pnpm publishPublish package to npm registry

Advanced Usage

Workspace Filtering

# Run build in a specific package
pnpm --filter my-app build

# Run tests in all packages matching pattern
pnpm --filter "*-lib" test

# Run in all packages that have changed since main
pnpm --filter "[main]" build

# Run in packages that depend on my-lib (dependents)
pnpm --filter "...my-lib" build

# Run in my-lib and all its dependencies
pnpm --filter "my-lib..." build

# Combine filters
pnpm --filter "my-app" --filter "my-lib" build

# Run in all workspace packages
pnpm -r build
pnpm -r --parallel build  # run in parallel

Dependency Patching

# Start patching a dependency
pnpm patch lodash@4.17.21

# After editing files in the temp directory printed above:
pnpm patch-commit /tmp/patch-dir

# This creates patches/lodash@4.17.21.patch and adds to package.json:
# "pnpm": { "patchedDependencies": { "lodash@4.17.21": "patches/lodash@4.17.21.patch" } }

Deploy Command (for Monorepos)

# Create a self-contained deployment directory
# Only includes the app and its workspace dependencies
pnpm --filter my-app deploy --prod ./deploy

# The deploy directory has its own node_modules with no symlinks
# Safe to copy to production server or Docker image

Catalog (pnpm 9+)

# pnpm-workspace.yaml — define shared version catalog
packages:
  - "packages/*"

catalog:
  react: "^18.3.0"
  typescript: "^5.4.0"
  vitest: "^1.6.0"
// packages/my-app/package.json
{
  "dependencies": {
    "react": "catalog:"
  },
  "devDependencies": {
    "typescript": "catalog:",
    "vitest": "catalog:"
  }
}

Content-Addressable Store

# View store location
pnpm store path

# Check store integrity
pnpm store status

# Remove unused packages (safe to run periodically)
pnpm store prune

# Add packages to store without linking (warm up cache)
pnpm fetch

Setting Up pnpm in Docker

FROM node:20-alpine

# Enable corepack and install pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate

WORKDIR /app

# Copy lockfile and manifests first for layer caching
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/my-app/package.json ./packages/my-app/
COPY packages/my-lib/package.json ./packages/my-lib/

# Install deps (uses frozen lockfile)
RUN pnpm install --frozen-lockfile

# Copy source
COPY . .
RUN pnpm --filter my-app build

Common Workflows

Setting Up a Monorepo

# Create root
mkdir my-monorepo && cd my-monorepo
pnpm init

# Create workspace config
cat > pnpm-workspace.yaml << 'EOF'
packages:
  - "packages/*"
  - "apps/*"
EOF

# Create packages
mkdir -p packages/ui apps/web
cd packages/ui && pnpm init && cd ../..
cd apps/web && pnpm init && cd ../..

# Add shared root devDependencies
pnpm add -D -w typescript vitest

# Add workspace package as dependency
pnpm --filter web add @myorg/ui --workspace

Adding Overrides to Fix Security Vulnerabilities

# Check what's vulnerable
pnpm audit

# Override the vulnerable version
# Add to package.json "pnpm.overrides":
# "vulnerable-pkg@<2.0.0": "^2.0.0"

pnpm install  # applies override
pnpm audit    # verify fix

Publishing Monorepo Packages

# Build all packages
pnpm -r build

# Publish all public packages
pnpm -r publish --access public

# Dry run to preview
pnpm -r publish --dry-run

Tips and Best Practices

Commit pnpm-lock.yaml — Always commit the lockfile. Use pnpm install --frozen-lockfile in CI to ensure deterministic installs. Set CI=true to enable this automatically.

Use pnpm dlx instead of npxpnpm dlx is the pnpm equivalent of npx and is faster because it leverages the pnpm store for caching.

Avoid shamefully-hoist — The default strict node_modules structure catches phantom dependencies (packages you use but didn’t declare). Only enable hoisting if a specific package explicitly requires it.

Use pnpm why to audit dependencies — Before adding a package, run pnpm why package to understand if it’s already a transitive dependency you could use directly.

Prune the store periodically — Run pnpm store prune monthly to remove packages that no longer appear in any lockfile. This keeps disk usage in check.

Pin pnpm version with packageManager — Add "packageManager": "pnpm@9.x.x" to your root package.json. With corepack enabled, this ensures all contributors use the same version.

{
  "packageManager": "pnpm@9.1.0+sha256...."
}

Use the catalog for version consistency — In pnpm 9+, the workspace catalog ensures every package in a monorepo uses exactly the same version of shared tools like TypeScript, Vitest, and React — with a single place to update them.

Run scripts in parallel carefullypnpm -r --parallel run build runs all builds simultaneously regardless of dependencies. Use --stream to see output in real time. For topology-aware ordering, use Turborepo or Nx on top of pnpm.