Just a reminder for me — what I install on a fresh MacBook Pro. The list used to be a pile
of brew install lines I copy-pasted and inevitably let drift; it’s now a single
Brewfile that replays the whole machine in one command.
Prerequisites
- Homebrew:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - Git
The Brewfile
brew bundle reads a Brewfile — a manifest of taps, formulae, casks, Mac App Store apps,
VS Code extensions and npm globals — and installs everything in it. It ships with Homebrew
itself, so the old brew tap homebrew/bundle step is no longer needed.
Creating it
On the machine you’re leaving, snapshot whatever is currently installed:
brew bundle dump --file=BrewfileThat writes a Brewfile in the current directory. The flags worth knowing:
| Flag | Effect |
|---|---|
-f, --force | Overwrite an existing Brewfile instead of refusing |
--no-describe | Drop the # description comment above each entry (they’re on by default) |
--no-vscode | Don’t record VS Code extensions |
-g, --global | Write to ~/.homebrew/Brewfile (or ~/.Brewfile) instead of the current directory |
Install mas before dumping if you want App Store apps in the file — brew bundle dump
only emits mas lines when the mas CLI is present:
brew install masThen commit the result. It’s a snapshot of installed state, it diffs cleanly, and
brew bundle dump -f after installing something new keeps it honest.
Anatomy
Mine is here — a straight dump, description comments and all. A representative slice:
tap "go-task/tap"
tap "romaintb/fgj", "https://codeberg.org/romaintb/homebrew-fgj.git"
tap "sgaunet/tools", trusted: true
# Resource monitor. C++ version and continuation of bashtop and bpytop
brew "btop"
# Task is a task runner/build tool that aims to be simpler and easier to use
brew "go-task", link: false
# Official GitLab CI runner
brew "gitlab-runner", restart_service: :changed
# A tool for backing up GitLab projects
brew "sgaunet/tools/gitlab-backup"
# Open-source code editor
cask "visual-studio-code"
vscode "golang.go"
vscode "anthropic.claude-code"
npm "@modelcontextprotocol/server-github"It’s a Ruby DSL, so # comments and blank lines are free and the ordering doesn’t matter.
Six entry types show up in practice:
tap— third-party taps, listed first because thebrewlines below resolve against them. A second argument gives a custom clone URL when the tap isn’t on GitHub.brew— formulae. Fully-qualified (sgaunet/tools/gitlab-backup) when they come from a tap.cask— GUI apps and fonts.mas— Mac App Store apps, e.g.mas "Amphetamine", id: 937984704. Get the ids withmas list.vscode— VS Code (and forks) extensions.npm— globally installed npm packages.
The trailing options are recorded automatically: link: false for a keg-only-by-choice
formula, restart_service: :changed to bounce a service when its formula updates, and
trusted: true for third-party taps you’ve run brew trust on — Homebrew keeps that in
~/.homebrew/trust.json and the Brewfile carries it across so a restore doesn’t stop to
ask.
Installing from it
On the fresh machine, once Homebrew is in place:
brew bundle --file=Brewfileinstall is the default subcommand, so that’s the same as brew bundle install --file=Brewfile.
If the file is named Brewfile and sits in the working directory, --file is redundant:
cd ~/dotfiles && brew bundleSet HOMEBREW_BUNDLE_FILE in ~/.zshrc to point at it from anywhere, or keep it at
~/.homebrew/Brewfile and use brew bundle -g.
Two things it can’t do for you: App Store entries need you signed into the App Store first, and casks for licensed apps (Alfred Powerpack, Moom, Bartender, CleanShot X, Dropzone) install the app but not your licence key.
Keeping it honest
brew bundle check --file=Brewfile # non-zero exit if anything is missing
brew bundle list --file=Brewfile # what the file declares
brew bundle add ripgrep # append an entry (--cask / --mas / --npm for the rest)
brew bundle cleanup --file=Brewfile # what's installed but *not* declaredcleanup only reports by default. brew bundle cleanup --force actually uninstalls
everything missing from the file — and resets the trust store to whatever the Brewfile
declares — which is an efficient way to lose something you’d forgotten to write down. Run
the reporting form first.
CLI tools (Homebrew)
The dump above is everything; this is the short list I’d want first on a bare machine:
brew install \
go-task goreleaser \
helm k9s kubectx \
sqlc usql dbmate libpq \
d2 rclone terraform \
wget pre-commit osv-scannerAdd libpq to PATH for pg_dump / psql — this bit the Brewfile can’t do for you:
echo 'export PATH="/opt/homebrew/opt/libpq/bin:$PATH"' >> ~/.zshrcGUI apps (Homebrew Cask)
brew install --cask \
visual-studio-code \
firefox \
docker \
warpMac App Store / direct downloads
I used to install these by hand. Almost all of them turn out to have a cask, so they belong
in the Brewfile too:
| App | Brewfile entry |
|---|---|
| Alfred | cask "alfred" |
| Rectangle | cask "rectangle" |
| Moom | cask "moom" |
| AltTab | cask "alt-tab" |
| Bartender | cask "bartender" |
| CheatSheet | cask "cheatsheet" |
| Dropzone 4 | cask "dropzone" |
| CleanShot X | cask "cleanshot" |
| Amphetamine | mas "Amphetamine", id: 937984704 |
The one leftover is Dropover — no cask for it, so it stays an App Store or direct download.