240 lines
11 KiB
Plaintext
240 lines
11 KiB
Plaintext
---
|
||
title: "Isolated installs"
|
||
description: "Strict dependency isolation similar to pnpm's approach"
|
||
---
|
||
|
||
Bun provides an alternative package installation strategy called **isolated installs** that creates strict dependency isolation similar to pnpm's approach. This mode prevents phantom dependencies (packages importing dependencies they never declared) and makes builds reproducible and deterministic.
|
||
|
||
Isolated installs are the default for **new** workspace/monorepo projects (with `configVersion = 1` in the lockfile). Existing projects continue using hoisted installs unless explicitly configured.
|
||
|
||
## What are isolated installs?
|
||
|
||
Isolated installs create a non-hoisted dependency structure where packages can only access their explicitly declared dependencies. This differs from the traditional "hoisted" installation strategy used by npm and Yarn, where dependencies are flattened into a shared `node_modules` directory.
|
||
|
||
### Key benefits
|
||
|
||
- **Prevents phantom dependencies** — Packages cannot accidentally import dependencies they haven't declared
|
||
- **Deterministic resolution** — Same dependency tree regardless of what else is installed
|
||
- **Better for monorepos** — Workspace isolation prevents cross-contamination between packages
|
||
- **Reproducible builds** — More predictable resolution behavior across environments
|
||
|
||
## Using isolated installs
|
||
|
||
### Command line
|
||
|
||
Use the `--linker` flag to specify the installation strategy:
|
||
|
||
```bash terminal icon="terminal"
|
||
# Use isolated installs
|
||
bun install --linker isolated
|
||
|
||
# Use traditional hoisted installs
|
||
bun install --linker hoisted
|
||
```
|
||
|
||
### Configuration file
|
||
|
||
Set the default linker strategy in your `bunfig.toml` or globally in `$HOME/.bunfig.toml`:
|
||
|
||
```toml bunfig.toml icon="settings"
|
||
[install]
|
||
linker = "isolated"
|
||
```
|
||
|
||
### Default behavior
|
||
|
||
The default linker strategy depends on your project's lockfile `configVersion`:
|
||
|
||
| `configVersion` | Using workspaces? | Default Linker |
|
||
| --------------- | ----------------- | -------------- |
|
||
| `1` | ✅ | `isolated` |
|
||
| `1` | ❌ | `hoisted` |
|
||
| `0` | ✅ | `hoisted` |
|
||
| `0` | ❌ | `hoisted` |
|
||
|
||
**New projects**: Default to `configVersion = 1`. In workspaces, v1 uses the isolated linker by default; otherwise it uses hoisted linking.
|
||
|
||
**Existing Bun projects (made pre-v1.3.2)**: If your existing lockfile doesn't have a version yet, Bun sets `configVersion = 0` when you run `bun install`, preserving the previous hoisted linker default.
|
||
|
||
**Migrations from other package managers**:
|
||
|
||
- From pnpm: `configVersion = 1` (using isolated installs in workspaces)
|
||
- From npm or yarn: `configVersion = 0` (using hoisted installs)
|
||
|
||
Override the default by passing the `--linker` flag or setting it in your configuration file.
|
||
|
||
## How isolated installs work
|
||
|
||
### Directory structure
|
||
|
||
Instead of hoisting dependencies, isolated installs create a two-tier structure:
|
||
|
||
```bash tree layout of node_modules icon="list-tree"
|
||
node_modules/
|
||
├── .bun/ # Central package store
|
||
│ ├── node_modules/ # Hoisted fallback (see install.hoist)
|
||
│ │ └── package -> ../[email protected]/node_modules/package
|
||
│ ├── [email protected]/ # Versioned package installations
|
||
│ │ └── node_modules/
|
||
│ │ └── package/ # Actual package files
|
||
│ ├── @[email protected]/ # Scoped packages (+ replaces /)
|
||
│ │ └── node_modules/
|
||
│ │ └── @scope/
|
||
│ │ └── package/
|
||
│ └── ...
|
||
└── package-name -> .bun/[email protected]/node_modules/package # Symlinks
|
||
```
|
||
|
||
### Resolution algorithm
|
||
|
||
1. **Central store** — Bun installs all packages in `node_modules/.bun/package@version/` directories
|
||
2. **Symlinks** — Top-level `node_modules` contains symlinks pointing to the central store
|
||
3. **Peer resolution** — Complex peer dependencies create specialized directory names
|
||
4. **Deduplication** — Packages with identical package IDs and peer dependency sets are shared
|
||
5. **Re-linking** — On later installs, Bun reuses existing store entries and re-points their symlinks if a dependency was re-resolved. Store entries that are no longer referenced stay until you run [`bun prune`](/pm/cli/prune)
|
||
|
||
### Workspace handling
|
||
|
||
In monorepos, Bun handles workspace dependencies specially:
|
||
|
||
- **Workspace packages** — Symlinked directly to their source directories, not the store
|
||
- **Workspace dependencies** — Can access other workspace packages in the monorepo
|
||
- **External dependencies** — Installed in the isolated store
|
||
|
||
## Comparison with hoisted installs
|
||
|
||
| Aspect | Hoisted (npm/Yarn) | Isolated (pnpm-like) |
|
||
| ------------------------- | ------------------------------------------ | --------------------------------------- |
|
||
| **Dependency access** | Packages can access any hoisted dependency | Packages only see declared dependencies |
|
||
| **Phantom dependencies** | ❌ Possible | ✅ Prevented |
|
||
| **Disk usage** | ✅ Lower (shared installs) | ✅ Similar (uses symlinks) |
|
||
| **Determinism** | ❌ Less deterministic | ✅ More deterministic |
|
||
| **Node.js compatibility** | ✅ Standard behavior | ✅ Compatible via symlinks |
|
||
| **Best for** | Single projects, legacy code | Monorepos, strict dependency management |
|
||
|
||
## Advanced features
|
||
|
||
### Peer dependency handling
|
||
|
||
Isolated installs encode peer dependencies in the store path:
|
||
|
||
```bash tree layout of node_modules icon="list-tree"
|
||
# Package with peer dependencies creates specialized paths
|
||
node_modules/.bun/[email protected][email protected]/
|
||
```
|
||
|
||
The directory name includes both the package version and its peer dependency versions, so each unique combination gets its own installation.
|
||
|
||
### Strict resolution with `install.hoist = false`
|
||
|
||
By default, Bun creates `node_modules/.bun/node_modules`, a fallback directory with a symlink to every installed package. Because this directory is an ancestor of every store entry, a package in the store can resolve dependencies it never declared. Set [`install.hoist = false`](/runtime/bunfig#install-hoist) (or `hoist=false` in `.npmrc`, matching pnpm) to skip creating this directory. Undeclared imports then fail instead of depending on what else happens to be installed. One caveat, shared with pnpm: the root `node_modules` also sits above the store. As a result, packages linked there (your direct dependencies, `publicHoistPattern` matches, and workspace packages) stay resolvable from any store package:
|
||
|
||
```toml bunfig.toml icon="settings"
|
||
[install]
|
||
linker = "isolated"
|
||
hoist = false
|
||
```
|
||
|
||
[`install.hoistPattern`](/runtime/bunfig#install-hoistpattern) and [`install.publicHoistPattern`](/runtime/bunfig#install-publichoistpattern) offer pattern-based control over the same fallback directory and the root `node_modules` respectively.
|
||
|
||
### Global virtual store
|
||
|
||
When [`install.globalStore`](/runtime/bunfig#install-globalstore) is enabled, Bun materializes store entries once into a [global virtual store](/pm/global-store) at `<cache>/links/`, and `node_modules/.bun/<pkg>@<ver>` is a symlink into it. Warm installs after `rm -rf node_modules` only create one symlink per package instead of copying every package's files again, which is roughly **7× faster** on a typical mid-size project. The global store is **off by default**; see the [global store docs](/pm/global-store) for how to enable it, the full layout, benchmarks, and tradeoffs.
|
||
|
||
### Backend strategies
|
||
|
||
When the global store is disabled (the default) or an entry isn't eligible for it, Bun materializes the entry under the project using one of:
|
||
|
||
- **Clonefile** (macOS) — Copy-on-write filesystem clones
|
||
- **Hardlink** (Linux/Windows) — Hardlinks to save disk space
|
||
- **Copyfile** (fallback) — Full file copies when other methods aren't available
|
||
|
||
### Debugging isolated installs
|
||
|
||
Enable verbose logging to see what an install is doing:
|
||
|
||
```bash terminal icon="terminal"
|
||
bun install --linker isolated --verbose
|
||
```
|
||
|
||
The verbose output shows:
|
||
|
||
- Store entry creation
|
||
- Symlink operations
|
||
- Peer dependency resolution
|
||
- Deduplication decisions
|
||
|
||
## Troubleshooting
|
||
|
||
### Compatibility issues
|
||
|
||
Some packages may not work correctly with isolated installs due to:
|
||
|
||
- **Hardcoded paths** — Packages that assume a flat `node_modules` structure
|
||
- **Dynamic imports** — Runtime imports that don't follow Node.js resolution
|
||
- **Build tools** — Tools that scan `node_modules` directly
|
||
|
||
If you encounter issues, you can:
|
||
|
||
1. **Switch to hoisted mode** for specific projects:
|
||
|
||
```bash terminal icon="terminal"
|
||
bun install --linker hoisted
|
||
```
|
||
|
||
2. **Report compatibility issues** to help improve isolated install support
|
||
|
||
### Performance considerations
|
||
|
||
- **Install time** — May be slightly slower due to symlink operations
|
||
- **Disk usage** — Similar to hoisted (uses symlinks, not file copies)
|
||
- **Memory usage** — Higher during install due to complex peer resolution
|
||
|
||
## Migration guide
|
||
|
||
### From npm/Yarn
|
||
|
||
```bash terminal icon="terminal"
|
||
# Remove existing node_modules and lockfiles
|
||
rm -rf node_modules package-lock.json yarn.lock
|
||
|
||
# Install with isolated linker
|
||
bun install --linker isolated
|
||
```
|
||
|
||
### From pnpm
|
||
|
||
Isolated installs are conceptually similar to pnpm, so migration is direct:
|
||
|
||
```bash terminal icon="terminal"
|
||
# Remove pnpm files
|
||
rm -rf node_modules pnpm-lock.yaml
|
||
|
||
# Install with Bun's isolated linker
|
||
bun install --linker isolated
|
||
```
|
||
|
||
The layouts are close: Bun hardlinks (clones on macOS) packages from its [global cache](/pm/global-cache) into a per-project store, `node_modules/.bun/`, and symlinks top-level `node_modules` entries into it. With [`install.globalStore`](#global-virtual-store) enabled, those store entries become symlinks into a global virtual store instead.
|
||
|
||
## When to use isolated installs
|
||
|
||
**Use isolated installs when:**
|
||
|
||
- Working in monorepos with multiple packages
|
||
- Strict dependency management is required
|
||
- Preventing phantom dependencies is important
|
||
- Building libraries that need deterministic dependencies
|
||
|
||
**Use hoisted installs when:**
|
||
|
||
- Working with legacy code that assumes flat `node_modules`
|
||
- Compatibility with existing build tools is required
|
||
- Working in environments where symlinks aren't well supported
|
||
- You prefer the traditional npm behavior
|
||
|
||
## Related documentation
|
||
|
||
- [Package manager > Workspaces](/pm/workspaces) — Monorepo workspace management
|
||
- [Package manager > Lockfile](/pm/lockfile) — Understanding Bun's lockfile format
|
||
- [CLI > install](/pm/cli/install) — Complete `bun install` command reference
|