CLI tool
zhaofengli/nix-homebrew avatar
zhaofengli/nix-homebrew

nix-homebrew installs Homebrew itself under a declarative macOS system

Homebrew installation manager for nix-darwin

782 stars60 forksNixMIT

At a glance

What is it?
nix-homebrew is a nix-darwin module that manages the Homebrew installation on macOS rather than the packages it installs, pinning the Homebrew version and optionally managing taps and trust entries declaratively. It suits people already running a declarative macOS configuration who want the brew binary itself to be reproducible, and it is not a package manager.
Who is it for?
Adopt nix-homebrew when you already run nix-darwin and want the Homebrew binary, its version and its taps to come from the same declarative configuration as the rest of the machine, especially on Apple Silicon where the architecture switch matters. Do not adopt it expecting it to install your packages, since the documentation says it only installs Homebrew itself, and use the homebrew options in nix-darwin for formula and cask management.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 11 days ago.
What is it written in?
Mainly Nix, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

It manages the installation, not the packages

nix-homebrew manages Homebrew installations on macOS using nix-darwin, and the distinction it draws in its own documentation is the first thing to understand. It only installs Homebrew itself and does not manage any package installed by it. For declarative formula and cask management, the documentation points you to the homebrew.* options in nix-darwin, which it says work well in tandem with nix-homebrew. So this is not a replacement for the homebrew.nix style approach. It is a layer underneath it, responsible for the brew binary, the prefix it lives in, the version of Homebrew that is fetched, and optionally the set of taps and the trust entries associated with them. The audience is therefore narrow and specific: a macOS user with a nix-darwin configuration who wants the tooling itself to be part of the declarative system rather than installed by a shell script that ran once and left state behind. The primary language of the repository is Nix, and the tree is laid out as a flake with modules/, pkgs/, scripts/, ci/ and an examples/ directory containing migrate.nix.

The flake input and the module that does the work

Using it starts with a requirement: you must have nix-darwin configured already. The flake input is added to your inputs, and if you want declarative tap management you also add the tap repositories as inputs with flake set to false:

nix
{
  inputs = {
    nix-homebrew.url = "github:zhaofengli/nix-homebrew";

    # Optional: Declarative tap management
    homebrew-core = {
      url = "github:homebrew/homebrew-core";
      flake = false;
    };
    homebrew-cask = {
      url = "github:homebrew/homebrew-cask";
      flake = false;
    };

    # (...)
  };
}

The module itself is nix-homebrew.darwinModules.nix-homebrew, and inside it the nix-homebrew attribute set carries the options. The core ones are enable, which installs Homebrew under the default prefix, user, which names the user owning the Homebrew prefix, and enableRosetta, described as Apple Silicon only, which also installs Homebrew under the default Intel prefix so that Rosetta 2 works. A new installation looks like this:

nix
nix-homebrew = {
  # Install Homebrew under the default prefix
  enable = true;

  # Apple Silicon Only: Also install Homebrew under the default Intel prefix for Rosetta 2
  enableRosetta = true;

  # User owning the Homebrew prefix
  user = "yourname";

  # Optional: Declarative tap management
  taps = {
    "homebrew/homebrew-core" = homebrew-core;
    "homebrew/homebrew-cask" = homebrew-cask;
  };
};

The `user` option is the one to check first, because Homebrew refuses to run as root and the prefix is owned by a specific account.

A unified launcher picks the right prefix per architecture

The most interesting consequence of the design is what happens on Apple Silicon. Once activated, a unified brew launcher is created under /run/current-system/sw/bin that automatically selects the correct Homebrew prefix to use based on the architecture. In other words, you type brew and get the native build on a native build, and the launcher handles the prefix decision for you. When you do need the other architecture, the documentation gives the explicit form: run arch -x86_64 brew to install X86-64 packages through Rosetta 2. That combination, a single brew on the path plus an explicit arch prefix when you want the other side, is what makes a two-prefix setup workable in practice rather than a configuration exercise. The enableRosetta option is what puts the second prefix in place, and on a machine without it the arch command has nothing to select. This is the part of the module with the most day to day effect, since every brew invocation on an Apple Silicon Mac goes through that launcher.

Taps are named the way Homebrew insists on

Declarative tap management has one sharp edge, and the documentation gives a concrete example of the fix. In Homebrew, the repo part of all taps always has homebrew- prepended, so brew tap user/repo makes a clone of the repository at github.com/user/homebrew-repo into the Taps directory of the repository. When you declare taps, the documentation asks you to name the key as a unique folder starting with homebrew-, and shows the correction as a diff:

diff
       nix-homebrew.taps = {
-        "mtslzr/marmaduke-chromium" = inputs.marmaduke-chromium;
+        "mtslzr/homebrew-marmaduke-chromium" = inputs.marmaduke-chromium;

The exact GitHub user and repo should almost always work. There is also an option to make tap management fully declarative, mutableTaps, set to false. With mutableTaps disabled, taps can no longer be added imperatively with brew tap, and removing a tap from the configuration means deleting the corresponding attribute and activating the new configuration. The documentation also suggests setting homebrew.taps to the attribute names of nix-homebrew.taps, which it says reduces configuration mismatches, and shows the small module that does it:

nix
({config, ...}: {
  homebrew.taps = builtins.attrNames config.nix-homebrew.taps;
})

The tradeoff is straightforward. Immutability gives you a reproducible tap set, and it also means the imperative escape hatch disappears for anyone who tries to add a tap on the fly.

Trust entries are added but never removed

Non-official taps require explicit trust under Homebrew, and nix-homebrew can write those entries during activation. The trust option takes four lists, for formulae, casks, commands and taps:

nix
nix-homebrew.trust = {
  formulae = [ "user/repo/formula" ];
  casks = [ "user/repo/cask" ];
  commands = [ "user/repo/command" ];

  # To quote upstream documentation:
  # > Trust a whole tap only when you are comfortable with all current and
  # > future formulae, casks and external commands from that tap being loaded
  # > by Homebrew.
  taps = [ "user/repo" ];
};

The quoted upstream warning is worth reading, because trusting a whole tap means trusting everything that appears in it later, including formulae you have never seen. The behaviour that needs care is the removal. The documentation states it twice, in the configuration comment and again in the prose: when you remove items from those lists, the corresponding trust entries are not removed automatically, and you need to use the brew untrust command to remove a trust entry. So the declarative configuration is additive for trust. An engineer who removes a tap from the configuration and assumes the machine is clean will find the trust entry still present. That is a security relevant detail, since a trust entry lets Homebrew load formulae and external commands from that tap.

Migrating an existing install and the cost of a custom prefix

If Homebrew was already installed with the official script, the module can migrate it. The autoMigrate option handles that, and the rest of the configuration is the same shape as a fresh installation:

nix
nix-homebrew = {
  # Install Homebrew under the default prefix
  enable = true;

  # Apple Silicon Only: Also install Homebrew under the default Intel prefix for Rosetta 2
  enableRosetta = true;

  # User owning the Homebrew prefix
  user = "yourname";

  # Automatically migrate existing Homebrew installations
  autoMigrate = true;
};

That path matters because a machine that already has Homebrew in the default prefix is the common case, and the alternative is a manual uninstall and reinstall. Extra prefixes are also configurable, with a prefixes option where each entry has a library path and its own taps. The documentation attaches a warning to this that is easy to skim and expensive to learn later: with a non-standard prefix you will no longer be able to use most bottles, meaning prebuilt packages. A custom prefix gives you isolation, and it also removes the binary distribution channel, so every package becomes a source build.

Maintenance state and what a consumer inherits

The repository is not archived and the last push was on 2026-10-12, so the module is being worked on. There are no GitHub releases, so there is no tagged version to pin in a flake input, and a flake that references github:zhaofengli/nix-homebrew follows the branch rather than a release. That is a deliberate choice for a flake, since a flake lock file records the resolved revision, and it also means an update arrives when someone bumps the lock. The licence is MIT, with a LICENSE file at the root, so the module can be vendored into your own configuration and modified, which is a reasonable answer for anyone who needs a different prefix layout or a policy tweak. Two things to check before adopting. The first is that the module is layered on nix-darwin, so you are committing to that configuration system regardless of how you feel about it, and the second is that the trust behaviour is additive, which means your real state is the union of the configuration and whatever brew untrust calls you have made. Neither is a reason to avoid it, but both should be understood before the module manages your machine.

Editorial conclusion

Adopt nix-homebrew when you already run nix-darwin and want the Homebrew binary, its version and its taps to come from the same declarative configuration as the rest of the machine, especially on Apple Silicon where the architecture switch matters. Do not adopt it expecting it to install your packages, since the documentation says it only installs Homebrew itself, and use the homebrew options in nix-darwin for formula and cask management. Verify first that the user option names the account owning the Homebrew prefix, that the tap keys use the homebrew- prefix Homebrew requires, and that you read the trust documentation, since removing a tap or a trust entry from the configuration does not remove it from the machine.

Frequently asked questions

Does nix-homebrew install Homebrew packages?

No. The documentation states that nix-homebrew only installs Homebrew itself and does not manage any package installed by it. For declarative formula and cask management it points to the homebrew.* options in nix-darwin, which work well alongside it.

What do I need before using nix-homebrew?

You must have nix-darwin configured already. You then add the nix-homebrew flake input, and the nix-homebrew.darwinModules.nix-homebrew module, with an enable option and a user option naming the account that owns the Homebrew prefix.

How does nix-homebrew handle Apple Silicon and Rosetta 2?

A unified brew launcher is created under /run/current-system/sw/bin that selects the correct Homebrew prefix based on the architecture. The enableRosetta option, described as Apple Silicon only, also installs Homebrew under the default Intel prefix, and you run arch -x86_64 brew to install X86-64 packages through Rosetta 2.

What happens to trust entries when I remove a tap from nix-homebrew?

The trust entries are not removed automatically. The documentation states that removing items from the trust lists leaves the corresponding entries in place, and that you need to use the brew untrust command to remove a trust entry.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. zhaofengli/nix-homebrew on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/zhaofengli-nix-homebrew.svg)](https://hysenlabs.com/projects/zhaofengli-nix-homebrew)

Was this useful?