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:
brew install wget # a formula — a command-line tool or library
brew install --cask slack # a cask — a GUI applicationFormulae 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.
| Aspect | Formula | Cask |
|---|---|---|
| What it installs | CLI tools and libraries (git, node, ffmpeg) | GUI macOS applications (Slack, Docker, VS Code) |
| Command | brew install <name> | brew install --cask <name> |
| Where it lives | Symlinked into your PATH | /Applications, like any normal Mac app |
The commands you'll actually use
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 dependenciesKeeping things updated
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 packagebrew 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:
brew cleanup # remove old versions and cached downloads
brew cleanup --dry-run # see what would be removed, without removing itManaging your setup declaratively with a Brewfile
For reproducing a setup across machines (or after a fresh install):
brew bundle dump # generate a Brewfile from what's currently installed
brew bundle install # install everything listed in a BrewfileA Brewfile looks like:
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
brew doctorRuns 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:
brew pin node
brew upgrade # skips node, upgrades everything else
brew unpin node # allows it to upgrade again laterbrew 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:
brew services list # see what's registered and its status
brew services start postgresql
brew services stop postgresql
brew services restart postgresqlbrew 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:
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
- Running
brew installwithsudo. Homebrew is explicitly designed to install without root privileges into a directory your user owns —sudo brew installis 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 doctorafter 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 upgradewith 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.
Related reading
- Setting Up a macOS Development Environment from Scratch — shares tags: macos, productivity (same category).
- Big O Notation Without the Math Panic — shares tags: productivity.
- Clean Code Principles That Actually Hold Up in Practice — shares tags: productivity.
- Understanding Cloud Cost Optimization Basics — shares tags: productivity.
- Git Hooks Explained: Automate Your Workflow — shares tags: productivity.