# bun prune (/pm/cli/prune)

<!-- agent-signals: reading_time_min: 2 · est_tokens: 830 · updated: 2026-09-23 -->
Related: [bun add](/pm/cli/add.md), [bun dedupe](/pm/cli/dedupe.md), [bun install](/pm/cli/install.md), [bun link](/pm/cli/link.md), [bun patch](/pm/cli/patch.md), [bun pm](/pm/cli/pm.md)

`bun prune` deletes everything in `node_modules` that the current `bun.lock` would not install: packages left behind after switching branches, removing a dependency, or installing with another package manager. With the isolated linker, this includes stale entries in `node_modules/.bun`.

`bun prune` never contacts the registry, never runs lifecycle scripts, and never modifies `bun.lock` or `package.json`.

```bash icon="terminal" title="terminal" terminal
bun prune
```

```
bun prune v1.4.0 (abc12345)

- @types/node@20.11.5
- left-pad@1.3.0
2 packages removed (checked 948 installed packages) [22.00ms]
```

Packages removed from a workspace or nested `node_modules` folder show the folder in parentheses, e.g. `- typescript@5.4.0 (packages/app/node_modules)`.

### `--production` [#--production]

Also remove everything `bun install --production` would not install (i.e. `devDependencies`). This lets you build with dev dependencies and ship without them:

```dockerfile
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build
RUN bun prune --production
```

`--omit=dev`, `--omit=optional`, and `--omit=peer` work the same way they do for `bun install`.

### `--dry-run` [#--dry-run]

List what would be removed without deleting anything:

```bash icon="terminal" title="terminal" terminal
bun prune --production --dry-run
```

```
bun prune v1.4.0 (abc12345)

- typescript@5.4.0
1 package can be removed (checked 948 installed packages) [9.00ms]
  bun prune --production
```

### `--filter` [#--filter]

Prune only the selected workspaces' `node_modules` folders (same patterns as [`bun install --filter`](/pm/filter)). Bun also cleans shared locations: the root `node_modules`, or `node_modules/.bun` with the isolated linker. In those locations, Bun keeps anything an unselected workspace still needs.

```bash icon="terminal" title="terminal" terminal
bun prune --production --filter app
```

### Notes [#notes]

* Always runs from the workspace root and covers every workspace's `node_modules`, even when invoked inside a workspace package.
* Requires `bun.lock` to match `package.json`. If you edited dependencies since the last install, run `bun install` first.
* Detects whether `node_modules` was laid out by the hoisted or the isolated linker and prunes it as it is. If the folders hold both layouts (for example after a switch of linkers without a clean install), Bun uses the linker `bun install` would use. Pass `--linker` to choose.
* Matches packages by name. If a package is at the wrong version, Bun leaves it for `bun install` to replace. Bun only removes a nested copy (`node_modules/a/node_modules/b`) once the correct version is installed above it; otherwise Bun keeps it and prints a warning.
* Never removes workspace folders, `.bin` entries still in use, dot-directories like `.cache`, plain files, or anything outside `node_modules`.
* Removes packages disabled for the current `os`/`cpu`. Pass `--os`/`--cpu` to prune for another platform.
* Works on a pruned monorepo checkout (e.g. `turbo prune` output) the same way `bun install --frozen-lockfile` does.
* If any entry fails to delete, the command still removes the rest and exits `1`.
* `--global` is not supported.
* To clean the global cache instead, use [`bun pm cache rm`](/pm/cli/pm#cache).
