macOSBeginner

Homebrew Explained: The macOS Package Manager for Developers

What Homebrew actually does, the difference between formulae and casks, and the commands you'll use most for managing packages on macOS.

DevFieldGuideJune 19, 2026 (updated July 28, 2026)6 min read
Share:

Nearly every macOS setup guide starts with "install Homebrew" — here's what it's actually doing and how to use it well beyond the initial install command.

What it solves

macOS doesn't ship with a package manager for developer tools the way Linux distros do. Homebrew fills that gap: it installs, updates, and removes command-line tools and libraries in a consistent way, tracking what's installed so you're not manually downloading and configuring binaries.

Formulae vs. casks

This distinction confuses people early on:

bash
brew install wget          # a formula — a command-line tool or library
brew install --cask slack   # a cask — a GUI application

Formulae are command-line tools, libraries, and their dependencies — things like git, node, ffmpeg. Casks are macOS GUI applications — things like Slack, Docker Desktop, Visual Studio Code. Both are managed through the same brew command, just with the --cask flag for the latter.

AspectFormulaCask
What it installsCLI tools and libraries (git, node, ffmpeg)GUI macOS applications (Slack, Docker, VS Code)
Commandbrew install <name>brew install --cask <name>
Where it livesSymlinked into your PATH/Applications, like any normal Mac app

The commands you'll actually use

bash
brew install <package>       # install
brew uninstall <package>     # remove
brew list                    # see everything installed
brew search <term>           # search available packages
brew info <package>          # see details, including dependencies

Keeping things updated

bash
brew update                  # update Homebrew's own package index
brew outdated                # see what has newer versions available
brew upgrade                 # upgrade everything outdated
brew upgrade <package>        # upgrade just one package

brew update updates Homebrew's knowledge of what versions exist; brew upgrade actually installs newer versions of what you have. Running update without upgrade won't change anything installed — a common point of confusion.

Cleaning up

Homebrew keeps old versions and cached downloads around by default, which adds up over time:

bash
brew cleanup             # remove old versions and cached downloads
brew cleanup --dry-run    # see what would be removed, without removing it

Managing your setup declaratively with a Brewfile

For reproducing a setup across machines (or after a fresh install):

bash
brew bundle dump             # generate a Brewfile from what's currently installed
brew bundle install           # install everything listed in a Brewfile

A Brewfile looks like:

ruby
brew "git"
brew "node"
cask "visual-studio-code"
cask "docker"

Checking this file into a dotfiles repo means a new machine can be brought up to your exact toolset with one command, instead of remembering (or re-Googling) what you had installed.

Troubleshooting: brew doctor

bash
brew doctor

Runs a diagnostic check and reports common configuration problems — permission issues, conflicting files, outdated Xcode Command Line Tools. Worth running whenever something Homebrew-related starts behaving strangely, before troubleshooting further by hand.

Pinning a package to avoid an unwanted upgrade

Sometimes a specific version needs to stay put — a project pinned to an older major version of a tool, or a package where the latest release has a known regression:

bash
brew pin node
brew upgrade   # skips node, upgrades everything else
brew unpin node   # allows it to upgrade again later

brew pin is scoped to exactly the package it's run against — it doesn't block brew upgrade from touching anything else, which is the detail that makes it safe to leave one package pinned indefinitely without it silently freezing your entire toolchain.

Homebrew Services: managing background processes

Some formulae (PostgreSQL, Redis, nginx) install a background service, not just a CLI tool, and Homebrew has a dedicated subcommand for managing those specifically, rather than reaching for a separate system tool:

bash
brew services list             # see what's registered and its status
brew services start postgresql
brew services stop postgresql
brew services restart postgresql

brew services start configures the service to launch automatically on login (via macOS's launchd), which is the detail that differs from just running the binary directly in a terminal — it persists across reboots without any extra setup, and brew services stop cleanly removes that autostart configuration along with stopping the running process.

Installing a specific older version

Homebrew's core formulae normally only carry the latest version forward, but brew install can target a specific version through a versioned formula name or a tap, when one is available:

bash
brew install node@20

@20-style formulae are maintained for major runtimes specifically because pinning to an older major version is common enough to need first-class support — installing this way keeps it independent of whatever brew install node (unversioned, latest) resolves to, letting both coexist if genuinely needed, though a version manager like nvm remains the better default for anything you expect to switch between often.

Homebrew is usually just the first step of a full macOS development environment setup — shell configuration, version managers, and Git/SSH all build on top of it once the package manager itself is in place.

Common mistakes

Common mistakes
  • Running brew install with sudo. Homebrew is explicitly designed to install without root privileges into a directory your user owns — sudo brew install is unnecessary and can leave file permissions in a state that causes confusing failures on the next normal (non-sudo) install.
  • Installing a GUI app's command-line tool separately instead of via its cask. Some casks (like Docker) bundle a CLI that's only correctly linked when installed through brew install --cask, not by downloading the app directly from the vendor's site.
  • Not running brew doctor after a macOS upgrade. OS upgrades can shift the Xcode Command Line Tools path or break symlinks Homebrew depends on — this is the single most common cause of "everything Homebrew related just stopped working" after an OS update.
  • Assuming brew upgrade with no arguments is fast. It re-evaluates and potentially upgrades every outdated formula and cask — on a machine that hasn't been updated in a while, this can take a long time and isn't the right move if you just need one specific package updated.
Advertisement

Frequently Asked Questions

Advertisement
DevFieldGuide
DevFieldGuide

Editorial Team

Practical tutorials and developer tools, written and maintained by the DevFieldGuide team.

Enjoyed this article?

Get the next one straight to your inbox, along with the best of what we publish each week.

Related Articles

More in macOS

View all