Homebrew is the duct tape that holds your macOS development environment together, letting you install almost anything without needing to be a root user.

Let’s see it in action. Imagine you need Node.js. Instead of hunting down installers, you just type:

brew install node

And just like that, Node.js, along with npm (its package manager), is installed in /usr/local/Cellar/node/<version>/ and symlinked into your PATH. You can check it:

node -v
npm -v

This is the core magic: Homebrew manages installations in a designated prefix (/usr/local by default for Intel Macs, /opt/homebrew for Apple Silicon), keeping your system Python and other built-in tools pristine. It downloads source code or pre-compiled binaries (called "bottles"), compiles them if necessary, and then creates symbolic links so the executables are available in your shell’s PATH.

The problem Homebrew solves is the "dependency hell" that plagues software development. Before Homebrew, installing a tool like imagemagick might require compiling its dependencies, which in turn require their dependencies, all with specific versions that could conflict with other software. Homebrew automates this by managing these dependencies for you. When you install imagemagick, Homebrew checks if its dependencies are already installed; if not, it installs them first.

Here’s how you can manage your Homebrew installations:

  • Updating Homebrew itself:

    brew update
    

    This fetches the latest list of available packages and Homebrew’s own updates. It’s like refreshing your app store.

  • Upgrading installed packages:

    brew upgrade
    

    This checks all your installed packages and upgrades them to their latest versions if available. It’s generally a good idea to run brew update before this.

  • Searching for packages:

    brew search nginx
    

    This helps you find if a particular piece of software is available.

  • Installing a package:

    brew install imagemagick
    

    As seen before, this downloads and installs the package and its dependencies.

  • Listing installed packages:

    brew list
    

    Shows you everything Homebrew has installed.

  • Uninstalling a package:

    brew uninstall imagemagick
    

    Removes the package and its symlinks. Homebrew is smart enough to know if a package is no longer needed as a dependency by another installed package.

  • Cleaning up old versions:

    brew cleanup
    

    Homebrew keeps older versions of packages around by default, which can be useful for rolling back. This command removes those old versions to save disk space.

The core components you interact with are brew itself, the "formulae" (definitions of how to install packages, written in Ruby), and "casks" (for GUI applications). You can even see the formula for node by running brew cat node. This Ruby script details the download URLs, checksums, and build steps.

When you install a package, Homebrew downloads it to a specific directory under its prefix (e.g., /opt/homebrew/Cellar/package-name/version). Then, it creates symbolic links from a common bin directory (e.g., /opt/homebrew/bin) to the actual executables within the Cellar. This way, your PATH environment variable only needs to point to /opt/homebrew/bin (or /usr/local/bin on Intel), and you can access any installed command.

One thing most people don’t realize is that Homebrew doesn’t just install command-line tools; it can also install graphical applications using brew install --cask <appname>. This uses "casks," which are like formulae but are designed for applications that are typically installed via .dmg files or drag-and-drop. You can install Google Chrome with brew install --cask google-chrome and it will be placed in your /Applications folder, managed by Homebrew.

After you’ve got the basics down, you’ll want to explore managing different versions of software, especially for languages like Python or Node.js, which is where tools like pyenv and nvm come in, often installed via Homebrew themselves.

Want structured learning?

Take the full Homebrew course →