Global cache

How Bun stores and manages packages in its global cache

Bun stores every package downloaded from the registry in a global cache at ~/.bun/install/cache, or the path set by the BUN_INSTALL_CACHE_DIR environment variable. Packages live in subdirectories named like ${name}@${version}, so Bun can cache multiple versions of a package.

Configuring cache behavior
bunfig.toml
[install.cache]
# the directory to use for the cache
dir = "~/.bun/install/cache"

# when true, don't load from the global cache.
# Bun may still write to node_modules/.cache
disable = false

# when true, always resolve the latest versions from the registry
disableManifest = false

Minimizing re-downloads#

When installing a package, if the cache already contains a version in the range specified by package.json, Bun uses the cached copy instead of downloading it again.

Installation details

If the semver version has a pre-release suffix (1.0.0-beta.0) or a build suffix (1.0.0+20220101), Bun replaces the suffix with a hash of that value instead, to reduce the chance of errors from long file paths.

When the node_modules folder exists, before installing, Bun checks that node_modules contains all expected packages with appropriate versions. If so, bun install completes. Bun uses a custom JSON parser which stops parsing as soon as it finds "name" and "version".

If a package is missing or has a version incompatible with the package.json, Bun checks for a compatible module in the cache. If the cache has one, Bun installs it into node_modules. Otherwise, Bun downloads the package from the registry, then installs it.


Fast copying#

Once a package is downloaded into the cache, Bun still needs to copy those files into node_modules. It uses the fastest syscalls available for this: hardlinks on Linux and Windows, clonefile on macOS.


Saving disk space#

On Linux and Windows, Bun uses hardlinks to "copy" a module into a project's node_modules directory, so the contents of the package only exist in a single location on disk. This greatly reduces the disk space used by node_modules.

The same applies on macOS, with a caveat. There Bun uses clonefile, which is copy-on-write: the clone occupies no extra disk space, but it counts towards the drive's limit. Because the copy only happens on write, patching node_modules/* in one project can't affect other installations.

Installation strategies

Configure this with the --backend flag, which all of Bun's package management commands respect.

  • hardlink: Default on Linux and Windows.
  • clonefile: Default on macOS.
  • clonefile_each_dir: Similar to clonefile, except it clones each file individually per directory. It is only available on macOS and tends to perform slower than clonefile.
  • copyfile: The fallback used when any of the above fail. It is the slowest option. On macOS, it uses fcopyfile(); on Linux it uses copy_file_range().
  • symlink: Symlinks each file instead of copying it. Only hoisted installs use it: --backend=symlink applies it to every package (macOS and Linux; Windows ignores the flag); without the flag, Bun uses it only for file: dependencies outside the project directory (for example file:../foo) and for transitive file: dependencies.

If you install with --backend=symlink, Node.js doesn't resolve node_modules of dependencies unless each dependency has its own node_modules folder or you pass --preserve-symlinks to node. See Node.js documentation on --preserve-symlinks.

terminal
bun install --backend symlink
node --preserve-symlinks ./foo.js

Bun's runtime also supports --preserve-symlinks.