zerobrew

English · 中文

zerobrew brings uv-style architecture to Homebrew packages on macOS and Linux.

Install

curl -fsSL https://zerobrew.rs/install | bash

The installer updates your shell config. After it finishes, restart your terminal or run the source command it prints.

Or via Homebrew:

brew install zerobrewhq/tap/zerobrew

Update zerobrew

If you used the standalone installer, rerun it:

curl -fsSL https://zerobrew.rs/install | bash
zb --version

If you installed with Homebrew:

brew update && brew upgrade zerobrew

If you installed from the old lucasgelfond/zerobrew tap, it's no longer updated. Switch to the new one:

brew uninstall zerobrew
brew untap lucasgelfond/zerobrew
brew install zerobrewhq/tap/zerobrew

The cachebag/zerobrew and zerobrewhq/zerobrew taps were moved to zerobrewhq/tap and keep updating; nothing to do.

Quick start

To opt in to a brew compatibility entrypoint:

zb install-alias brew
brew install jq
zb install-alias brew --remove

This creates brew -> zb-brew beside zb; ensure that directory is on PATH. You can also enable it during installation:

curl -fsSL https://zerobrew.rs/install | bash -s -- --brew-compat

Enabling it refuses existing Homebrew or other brew entrypoints on PATH and standard Homebrew locations, and never replaces an existing file. Removing it only deletes zerobrew's own symlink. If zb-brew is missing, reinstall zerobrew.

The entrypoint supports install, uninstall, list, info, outdated, upgrade, doctor, bundle, and update, using the arguments and output supported by zb. cleanup runs zb gc and only removes unreferenced store entries. update refreshes the formula index; it does not update zerobrew itself. Commands such as search, services, and tap, and flags such as --cask and list --formula, are not implemented and fail with a compatibility error. zb migrate requires real Homebrew on PATH and refuses this entrypoint.

zb install jq                   # install one package
zb install wget git             # install multiple
zb bundle                       # install from Brewfile
zb bundle install -f myfile     # install from custom file
zb bundle dump                  # export installed packages to Brewfile
zb bundle dump -f out --force   # dump to custom file (overwrite)
zb uninstall jq                 # uninstall one package
zb outdated                     # list packages with newer versions
zb upgrade                      # upgrade all outdated packages
zb upgrade jq wget              # upgrade specific packages
zb reset                        # uninstall everything
zb gc                           # garbage collect unused store entries
zbx jq --version                # run without linking

Automatic initialization (--auto-init or ZEROBREW_AUTO_INIT=true) and zb reset create data directories without changing shell configuration. Run zb init explicitly to configure your shell, or zb init --no-modify-path to leave it unchanged.

Performance snapshot

PackageHomebrew (cold)ZB (cold)Cold speedupHomebrew (warm)ZB (warm)Warm speedup
Overall (100 packages)853s109s7.8x756s29.9s25x
python@3.1417.85s1.86s9.6x17.10s624ms27.4x
node32.46s2.08s15.6x31.18s889ms35.1x
tesseract39.87s4.37s9.1x38.24s2.86s13.4x
openjdk34.14s6.09s5.6x31.67s2.81s11.3x
llvm17.09s6.09s2.8x10.51s172ms61.1x

Measured on 2026-10-23 with zerobrew 0.4.2 and Homebrew 7.0.10 on macOS 26.6.2, MacBook Pro (M3 Pro, 18 GB RAM), wired, ~436 Mbit/s download bandwidth. Homebrew started with nothing installed.

  • Cold: the package and all of its dependencies uninstalled, empty download cache.
  • Warm: the package and all of its dependencies uninstalled, downloads from the cold run still cached.
  • The median package was 7.5x faster cold and 62x faster warm. Per package, cold ranged from 1.7x (go) to 27x (certifi) and warm from 4.0x (fontconfig) to 275x (p11-kit). 86 of the 100 packages installed at least 5x faster cold, and 19 installed 100x faster or more warm.
  • Both tools run post-install steps. ca-certificates' step builds the CA bundle from the system keychain, which costs Homebrew about 8 seconds and used to cost zerobrew the same on every package that depends on it, most of the table. Since 0.4.2 zerobrew builds that bundle in process, byte for byte, in well under a second; the run from before that (results/2026-10-12) was 3.6x cold. fontconfig's fc-cache is the same kind of step and is not ported yet (#448), which is what holds fontconfig, harfbuzz, cairo and pango under 10x warm.
  • Cold installs are bound by the link. The same run on a ~80 Mbit/s connection two days earlier (results/2026-10-21) came out at 3.7x cold overall, with go and llvm at the speed of the download, while the median package was still 6.2x. Homebrew's total barely moves with bandwidth; zerobrew's is mostly bandwidth.
  • Warm installs use more disk. After an uninstall brew keeps the bottle, we keep the bottle and the unpacked keg in the store. That is the main tradeoff here.
Full results
PackageHomebrew (cold)ZB (cold)Cold speedupHomebrew (warm)ZB (warm)Warm speedup
Overall (100 packages)852.82s108.73s7.84x755.53s29.91s25.26x
ca-certificates8.35s361ms23.13x7.69s29ms265.28x
openssl@311.44s1.15s9.92x10.58s63ms167.95x
xz2.43s391ms6.22x1.62s28ms57.93x
sqlite3.10s450ms6.90x2.31s27ms85.44x
readline2.40s524ms4.57x1.62s26ms62.23x
icu4c@783.00s871ms3.44x1.94s27ms71.96x
python@3.1417.85s1.86s9.61x17.10s624ms27.40x
awscli31.02s3.05s10.17x29.72s884ms33.62x
node32.46s2.08s15.58x31.18s889ms35.07x
harfbuzz25.17s4.16s6.05x23.51s2.80s8.40x
ncurses3.04s779ms3.90x2.28s48ms47.54x
gh2.33s584ms3.98x1.47s36ms40.83x
pcre22.54s484ms5.25x1.75s33ms52.91x
libpng2.46s348ms7.06x1.69s27ms62.41x
zstd3.93s431ms9.13x3.14s34ms92.32x
glib6.91s924ms7.48x5.89s98ms60.09x
lz42.41s311ms7.75x1.68s26ms64.65x
gettext4.53s951ms4.76x3.66s65ms56.25x
libngtcp212.29s1.50s8.22x11.43s516ms22.15x
libnghttp32.42s351ms6.89x1.67s26ms64.04x
pkgconf2.47s311ms7.95x1.68s27ms62.07x
libunistring2.45s421ms5.81x1.69s27ms62.63x
mpdecimal2.46s314ms7.84x1.67s26ms64.27x
brotli2.48s375ms6.61x1.69s27ms62.41x
jpeg-turbo2.51s420ms5.97x1.71s28ms60.96x
xorgproto2.14s360ms5.95x1.36s29ms46.79x
ffmpeg22.61s1.73s13.04x21.20s592ms35.81x
cmake3.19s1.02s3.12x1.89s80ms23.56x
libnghttp22.46s313ms7.87x1.67s26ms64.04x
go4.79s2.77s1.73x3.50s169ms20.74x
uv2.37s606ms3.91x1.44s26ms55.23x
gmp2.46s446ms5.51x1.69s27ms62.44x
libtiff8.00s541ms14.78x7.17s54ms132.81x
fontconfig10.75s3.60s2.98x9.79s2.47s3.96x
python@3.1315.95s1.60s9.97x15.26s189ms80.74x
git6.93s1.17s5.92x5.52s103ms53.56x
little-cms28.76s546ms16.05x8.00s56ms142.79x
dav1d2.48s330ms7.52x1.71s26ms65.62x
openexr11.36s638ms17.80x10.40s66ms157.59x
c-ares2.50s330ms7.56x1.73s33ms52.42x
tesseract39.87s4.37s9.13x38.24s2.86s13.38x
p11-kit10.11s446ms22.68x9.34s34ms274.65x
imagemagick17.23s3.10s5.57x16.23s84ms193.27x
zlib2.42s307ms7.87x1.66s26ms63.69x
libx116.10s828ms7.37x5.32s184ms28.91x
freetype3.94s438ms9.00x3.20s32ms100.03x
protobuf4.06s576ms7.06x3.30s62ms53.19x
gnupg25.70s1.07s23.95x24.34s189ms128.76x
openjph8.81s554ms15.90x8.00s56ms142.82x
libtasn12.45s338ms7.25x1.71s28ms61.00x
ruby18.05s3.84s4.70x17.75s1.34s13.28x
gnutls18.21s978ms18.62x16.46s139ms118.40x
expat2.48s310ms8.01x1.67s27ms61.93x
libsodium2.49s337ms7.39x1.71s27ms63.44x
simdjson2.49s405ms6.15x1.69s26ms65.08x
gemini-cli33.61s2.25s14.91x32.56s917ms35.50x
libarchive5.44s464ms11.73x4.74s37ms128.08x
pyenv15.43s1.69s9.16x14.56s544ms26.76x
pixman2.64s345ms7.64x1.71s27ms63.33x
curl19.71s1.61s12.20x18.61s570ms32.65x
opus2.43s345ms7.05x1.69s26ms65.12x
unbound14.67s1.54s9.51x13.73s554ms24.79x
cairo22.24s4.03s5.52x20.97s2.77s7.56x
pango28.60s4.10s6.97x27.39s2.79s9.80x
leptonica10.53s624ms16.88x9.85s65ms151.58x
libxcb5.18s755ms6.86x4.39s139ms31.55x
jpeg-xl15.14s720ms21.03x14.04s72ms194.96x
coreutils13.36s1.60s8.37x12.38s529ms23.41x
certifi9.07s342ms26.54x8.34s37ms225.51x
krb512.54s1.50s8.33x11.66s510ms22.86x
docker2.25s533ms4.21x1.39s27ms51.48x
libheif12.14s736ms16.49x11.27s66ms170.77x
webp4.68s432ms10.84x3.95s35ms112.74x
libxext6.97s804ms8.67x6.20s189ms32.80x
libxau2.86s346ms8.26x2.15s32ms67.31x
gcc11.91s2.48s4.80x9.94s72ms138.01x
bzip22.10s300ms7.01x1.31s26ms50.54x
libxdmcp2.89s349ms8.28x2.14s34ms63.03x
abseil3.02s545ms5.54x2.18s54ms40.37x
xcbeautify2.13s371ms5.74x1.35s27ms49.93x
libuv2.50s340ms7.34x1.71s27ms63.30x
giflib2.43s293ms8.31x1.69s27ms62.67x
utf8proc2.46s313ms7.88x1.68s26ms64.62x
libxrender6.94s798ms8.69x6.14s182ms33.74x
m42.11s292ms7.21x1.31s26ms50.42x
graphite22.42s304ms7.97x1.68s26ms64.77x
openjdk34.14s6.09s5.60x31.67s2.81s11.26x
uvwasi3.20s328ms9.75x2.45s29ms84.52x
libffi2.47s329ms7.50x1.67s26ms64.31x
libdeflate2.47s330ms7.47x1.68s26ms64.73x
llvm17.09s6.09s2.81x10.51s172ms61.10x
aom3.35s529ms6.33x2.50s32ms78.19x
lzo2.48s289ms8.57x1.69s27ms62.74x
libevent12.35s1.47s8.38x11.50s507ms22.69x
libgpg-error5.26s861ms6.11x4.45s71ms62.68x
libidn25.28s765ms6.90x4.41s72ms61.29x
berkeley-db@53.96s1.44s2.75x2.84s57ms49.81x
deno11.83s1.08s10.97x10.80s59ms183.03x
libedit2.46s329ms7.47x1.68s26ms64.69x
oniguruma2.49s328ms7.60x1.68s26ms64.62x

To reproduce, run just bench --full results/ on a machine with nothing installed in Homebrew. It resets zerobrew, uninstalls everything it installed in Homebrew, and writes a table to results/benchmark.md.

A smaller version runs in CI: the parity workflow installs a fixed set of formulae with both tools on the same runner and compares the resulting prefixes byte for byte, and the timing workflow times those formulae plus node and python@3.14, cold and warm, with both tools and with the last zerobrew release.

The nightly timing run fails if zerobrew is less than 1.5x faster than Homebrew cold or 3x warm on any of them, or more than 20% slower than the last release overall.

A note on how we are achieving these speeds

WRT Homebrew, I want it to be clear that both brew and zerobrew install the same bottles from Homebrew's build farm.

The difference is what happens after the download: Homebrew runs Ruby to evaluate the formula, unpacks, rewrites paths with install_name_tool, and re-signs each binary one at a time, while zerobrew does the relocation in-process and links from a content-addressed store, so it pays for the download once and almost nothing after.

I want it to be clear that we are basically nothing without Homebrew's bottle build farm. Every package zerobrew installs is one they built, tested and published. We don't compile anything, we don't maintain formulae, and none of these numbers exist without that work. The speed comes from how the artifacts are installed, not from what's in them.

This is a good read, if you're interested in what zerobrew is and isn't: Standing on the shoulders of Homebrew by Andrew Nesbitt. It makes the point better than I can.

nanobrew:

nanobrew’s published numbers time a “warm install” of a package that is already installed, which is an early exit, not an install, and compare against zerobrew 0.1.0.

Every number above is a real install from an uninstalled state, with the script and the full log in this repo, so you can run it yourself.

Relationship with Homebrew

zerobrew is more of a performance-optimized client for the Homebrew ecosystem. We rely on:

  • Homebrew's formula definitions (homebrew-core)
  • Homebrew's pre-built bottles when available
  • Homebrew's package metadata and infrastructure

Our innovations focus on:

  • Content-addressable storage for deduplication
  • APFS clonefiles for zero-overhead copying
  • Source build fallback using Homebrew's Ruby DSL

zerobrew is experimental. We recommend running it alongside Homebrew rather than as a replacement, and do not recommend purging homebrew and replacing it with zerobrew unless you are absolutely sure about the implications of doing so.

Project status

  • Status: Experimental, but quite useful. I (@cachebag) daily drive it myself.
  • Feedback: If you hit incompatibilities, please open an issue or PR.
  • License: Dual-licensed under Apache 2.0 OR MIT, at your choice. The source build shim and the post-install runner are derived from Homebrew and are covered by its BSD 2-Clause license.