# nanobrew: a Zig package manager that replaces Homebrew on the fast path and keeps it as fallback

> nanobrew is a macOS and Linux package manager written in Zig that installs the top 100 Homebrew formulae and top 100 casks natively, without Ruby or a brew subprocess, and falls back to verified Homebrew for everything else. The project publishes its own benchmark numbers, and the caveat it states about itself is the important part: it does not run Homebrew's Ruby post_install hooks.

**justrach/nanobrew** — The fastest macOS package manager. Written in Zig. 3ms warm installs.

- Repository: https://github.com/justrach/nanobrew
- Website: https://nanobrew.trilok.ai
- Stars: 1,138 · Forks: 21
- Language: Zig
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/justrach-nanobrew

## The premise: Homebrew is a Ruby program, and that is the bottleneck

nanobrew is described as a fast package manager for macOS and Linux, written in Zig, and its argument with Homebrew is mostly about the runtime rather than about the package format.

The comparison table in the project documentation puts the runtime row in plain terms. Homebrew is Ruby, about 57 MB, while nanobrew is a single 1.2 MB static binary with instant startup and no bootstrapping. The stated consequence is 6.8 times smaller than the alternative named zerobrew and 47 times smaller than Homebrew. The project points at its own benchmark workflow for how those numbers are measured.

The other structural difference is the auto-update behaviour. Homebrew's brew install runs brew update first, which the documentation says can take minutes, whereas nb install just installs, and self-update is explicit through nb update. That is a real workflow difference rather than a micro-optimisation, because the implicit update on every install is the part of Homebrew that people notice most when they are waiting on a command.

The third difference is Gatekeeper. Homebrew casks get the com.apple.quarantine flag, which triggers the are you sure dialog when you open the app, while nanobrew does not set the flag, so apps open immediately. That is a genuine macOS specific behaviour difference, and it cuts both ways, since the quarantine flag exists for a reason and disabling it removes a prompt that was designed to warn you.

The scope is stated in the opening line of the project description. The native install pipeline covers the top 100 Homebrew formulae and top 100 casks, with no brew and no Ruby, and there is a verified Homebrew fallback for everything else, plus native.deb support for Linux and Docker.

So the architecture is a fast path plus a fallback rather than a reimplementation of everything. The licence is Apache-2.0, the primary language is Zig, and the repository is not archived with the last push on 2026-10-19.

## The install paths, and the self-update bug you will hit on old versions

There are three ways to install, and they are short.

The one-liner pipes a script into bash:

```bash
# One-liner
curl -fsSL https://nanobrew.trilok.ai/install | bash
```

Or through Homebrew itself:

```bash
# Or via Homebrew
brew tap justrach/nanobrew https://github.com/justrach/nanobrew
brew install nanobrew
```

Or from source, which needs Zig 0.16.0 or later:

```bash
# Or build from source (needs Zig 0.16.0+)
git clone https://github.com/justrach/nanobrew.git
cd nanobrew && ./install.sh
```

The second option is the interesting one and it is a nice piece of engineering pragmatism. A tool whose pitch is that you do not need Homebrew can still be installed through Homebrew, which means the transition does not require anyone to change how they bootstrap their machine, and the tap can be removed later.

The upgrading section carries a warning that is worth reading before you plan a rollout. From v0.1.193 and later, self-update works in one call with nb update. If you are on v0.1.192 or older and nb update errors with could not download SHA256 checksum, the self-updater on your installed binary cannot get past a redirect-chain bug in its native HTTP client, and the fix is to re-run the installer once, after which nb update works for all future releases.

The same bug affected exactly v0.1.190 through a different code path, a file-naming bug in the extract step, and the same one-liner unsticks it. So there are two specific broken versions with two different root causes, and the recovery is the same in both cases. That is a candid thing for a project to document about itself, and it is the kind of detail that saves a support round.

The release tags show a busy project with an unusual release naming. The three most recent are a macOS pilot tag requesting Monterey testing on Intel Macs, v0.1.212 with Intel packages and upstream binary resolution, and v0.1.212-rc.1 for native Intel package validation. The pilot tag is a test request rather than a product release, which tells you the project is actively recruiting Intel Mac users for compatibility testing.

## The command surface, and version pinning with name@version

The daily commands are short and follow a familiar shape, with the nb prefix standing in for brew.

```bash
nb install tree               # install a package
nb install ffmpeg wget curl   # install multiple at once
nb install --shims yt-dlp     # expose yt-dlp, keep dependency tools private
nb remove tree                # uninstall
nb list                       # see what's installed
nb info jq                    # show package details
nb search ripgrep             # search formulas and casks
```

The --shims flag is worth pausing on, because it is a design decision rather than a convenience. It exposes one binary on your PATH and keeps the dependency tools private. For a package like yt-dlp, which brings its own dependency tools, that is the behaviour you want, and it is the sort of thing a package manager that models the Homebrew ecosystem closely has to handle.

Version pinning is the other feature that goes beyond a thin wrapper. You can pin any Homebrew formula to an exact version with the name@version syntax:

```bash
nb install hexyl@0.17.0       # install exactly hexyl 0.17.0
nb install wget@1.21.3        # install exactly wget 1.21.3
```

The mechanism is that nanobrew resolves the requested version from the bottles Homebrew keeps in its container registry, which means it reuses Homebrew's own storage rather than maintaining a separate archive. And a versioned install is auto-pinned, so a later nb upgrade will not replace your chosen version, with nb unpin <pkg> to allow upgrades again.

That auto-pin behaviour is a genuinely thoughtful detail. The failure mode of version pinning in most tools is that the pin silently stops being a pin, and here the tool protects it by default and gives you an explicit command to release it.

The documented limits are specific. Version pinning works for Homebrew formulae, on macOS and Linux bottles, and casks with the --cask flag and deb packages with the --deb flag do not support version pinning yet. The documentation also warns that if the exact version is not available as a bottle for your platform, old bottles can be garbage collected, and in that case nb lists the available ones.

## The Linux and Docker path, and the NBIX index that makes it fast

The --deb mode is described as a full apt-get replacement: it fetches APT package indices, resolves dependencies, downloads and extracts .deb files, all in pure Zig with no subprocess calls.

The published Linux numbers are in a table, with the note that they are verified benchmarks on Ubuntu 24.04.4 LTS on aarch64 under Docker and Colima, the median of 3 runs, with warm meaning a populated NBIX binary index cache and cached .deb blobs, and --skip-postinst. The claim is up to 13 times faster than apt-get on warm installs, with the git vim build-essential set of 116 dependencies going from 43,833ms to 3,402ms.

The mechanism behind that is worth understanding, and it comes down to one thing. The NBIX binary index cache deserializes 70K packages in 32ms, against what the project describes as 3 seconds of HTTP plus 72MB of gzip decompression plus text parsing. So the win is not in the downloading, it is in not re-parsing the package index on every invocation.

Three other things contribute and they are all straightforward engineering. There are 8-thread parallel .deb downloads with HTTP connection reuse, 8-thread parallel extraction using concurrent ar, gzip and tar parsing through a native Zig tar implementation, and an arena allocator where a single deinit() frees all 70K parsed packages.

That last one is the kind of detail that only matters in Zig. In a garbage collected language, the 70K parsed package structures would be garbage and you would not think about it, but in Zig every allocation is manual, so the arena is the mechanism that makes a large parse cheap to throw away. The project is using the language's allocation model as a feature rather than working around it.

The macOS benchmark table works the same way, with a tree package of 0 dependencies, ffmpeg with 11 and wget with 6, and the note that the benchmarks were run on Apple Silicon on GitHub Actions macos-14 on 2026-03-23 and are auto-updated weekly. The warm install column is where nanobrew separates itself, with tree at 9ms and wget at 27ms, against Homebrew's 4.070s and 3.935s cold.

The repository layout supports the story. There is a bench/ directory, a registry/ directory, a worker/ directory, a Formula/ directory, a nb-linux file at the top level, a build.zig build script, and a src/ directory, plus BENCHMARKs.md as a separate document, so the performance claims are meant to be checkable rather than asserted.

## The limits, and the honest comparison the project makes with itself

The most useful paragraph in this project is the one that names what nanobrew does not do.

nanobrew does not run Homebrew's Ruby post_install hooks, so formulae that depend on them can be left incomplete. If you rely on post_install hooks, build-from-source options or Mac App Store integration, the documentation says to use Homebrew. nanobrew covers the fast path, which it defines as bottles, casks and bundles.

That is the single most important thing to check before you adopt this, and it is a sharp limit rather than a soft one. A post_install hook is where a formula does the work that cannot be expressed as a bottle, and a formula that is left incomplete is worse than one that failed to install, because the failure is silent.

The trade-off table makes the same point in a different way. On Brewfile no-ops, brew bundle rechecks everything, around 10 seconds even when satisfied, while nb bundle install returns instantly when there is nothing to do. And on parallel downloads, Homebrew has been concurrent by default since November 2025 through HOMEBREW_DOWNLOAD_CONCURRENCY, so the parallel advantage is not as unique as it first appears.

The framing of the comparison is also worth noting. The project says Homebrew is great software and powers millions of dev machines, and that nanobrew makes different tradeoffs. That is not the tone of a drop-in replacement announcement, it is the tone of a complementary tool, and the fallback design reinforces it, since anything outside the top 100 formulae and top 100 casks is handed to Homebrew.

Third-party taps are supported with nb install user/tap/formula, and the project claims to be the only fast Homebrew client with tap support. That is a competitive claim stated without qualification, and the tap path is worth testing yourself if you use a tap outside the core repository.

One more behaviour to know about is the no auto-update rule. It is a deliberate choice and a good one for CI, since a build that silently updates its own package index is a build whose results change. The cost is that you are responsible for running nb update, and the redirect bug described above makes that a real chore on older versions.

For a team, the safe adoption path is to keep Homebrew installed, add nanobrew alongside it, and point only the formulae that do not use post_install hooks at the new tool. That gets the warm install speed without betting on the fallback being exercised correctly on a machine you cannot watch.

## Conclusion

nanobrew fits a developer or CI runner that installs a small, fixed set of Homebrew packages repeatedly and cares about the seconds spent on a warm install, on macOS or Linux, and who does not depend on post_install hooks. It is the wrong choice if any of your formulae use Ruby post_install hooks, need build-from-source options or Mac App Store integration, since the project itself says to use Homebrew in that case, and it is the wrong choice for casks and deb packages that need version pinning, which is not supported yet. Before adopting, check whether any formula in your Brewfile relies on a post_install hook, verify the exact version pinning path for your platform, and read the self-update caveat, since versions up to 0.1.192 have a redirect bug that breaks nb update.

## FAQ

### What is nanobrew and how is it different from Homebrew?

nanobrew is a package manager for macOS and Linux written in Zig. It installs the top 100 Homebrew formulae and top 100 casks natively with no Ruby and no brew subprocess, and falls back to verified Homebrew for everything else. It is a single 1.2 MB static binary against Homebrew's roughly 57 MB Ruby runtime.

### How do I install nanobrew?

With curl -fsSL https://nanobrew.trilok.ai/install | bash, or through Homebrew itself with brew tap justrach/nanobrew https://github.com/justrach/nanobrew followed by brew install nanobrew, or from source by cloning the repository and running ./install.sh, which needs Zig 0.16.0 or later.

### Can nanobrew install a specific version of a package?

Yes, using the name@version syntax, for example nb install hexyl@0.17.0. The version is resolved from the bottles Homebrew keeps in its container registry, and a versioned install is auto-pinned so a later nb upgrade will not replace it. Casks and deb packages do not support version pinning yet.

### What does nanobrew not do that Homebrew does?

It does not run Homebrew's Ruby post_install hooks, so formulae that depend on them can be left incomplete. The documentation advises using Homebrew if you rely on post_install hooks, build-from-source options or Mac App Store integration, and says nanobrew covers the fast path of bottles, casks and bundles.

## Sources

- [justrach/nanobrew on GitHub](https://github.com/justrach/nanobrew)
- [License: Apache-2.0](https://github.com/justrach/nanobrew/blob/main/LICENSE)
- [Project website](https://nanobrew.trilok.ai)
- [README](https://github.com/justrach/nanobrew/blob/main/README.md)
- [Releases](https://github.com/justrach/nanobrew/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/justrach-nanobrew
