Reproduce a macOS dev setup with Homebrew Brewfile and brew bundle
Learn how to use Homebrew’s Brewfile and brew bundle to turn ad‑hoc macOS installs into a version‑controlled, reproducible developer setup.
14 Sept 2025, 00:09 UTC

Problem: manual installs waste time and drift
When a new teammate joins, setting up a development laptop often means following a wiki page, copying commands, and hoping nothing is missed. Over time, the machine accumulates packages that were installed for a one‑off test and never removed, making the environment hard to recreate.
Thesis: a Brewfile turns ad‑hoc installs into version‑controlled infrastructure
Homebrew’s brew bundle command reads a Brewfile—a simple Ruby DSL that lists tap, brew, cask, and mas entries—and ensures the declared software is present. By committing the Brewfile alongside dotfiles, you get a single source of truth for the base toolchain.
How Brewfile works
tapadds a third‑party repository (e.g.,tap 'homebrew/cask').brewinstalls a formula (e.g.,brew 'git').caskinstalls a macOS GUI app (e.g.,cask 'visual-studio-code').masinstalls a Mac App Store app using its numeric ID (e.g.,mas 'Slack', id: 803453959).
Running brew bundle in the directory containing the Brewfile installs anything missing and upgrades outdated formulae to the latest version that satisfies the declaration.
Worked example: onboarding a new hire
- Clone the dotfiles repository that contains a
Brewfileat its root. - Open a terminal and change to that directory:
- Run the bundle command. You need read access to the repository and the ability to install software via Homebrew (typically your user account).
- Add any taps listed.
- Install each formula or cask that is not already present.
- Upgrade installed formulae to the newest version that matches the declaration (if you omitted a version constraint).
- Prompt you to sign into the App Store if a
masentry requires it. - Verify that everything declared is present:
cd ~/dotfiles
brew bundle
Homebrew will:
brew bundle check
If the command exits with code 0, all items are satisfied. A non‑zero exit code lists missing entries, which you can feed into a setup script to fail fast.
Keeping the Brewfile clean
The dump command brew bundle dump --force writes a Brewfile that reflects every package currently installed, including dependencies you may not want to track. Instead of committing the raw dump, edit the file to keep only the top‑level tools you deliberately choose (e.g., your editor, language runtimes, and essential utilities). This reduces noise and makes future diffs meaningful.
Trade‑off: additive by default
brew bundle installs what is declared but does not remove packages that are not in the file. If you expect a fully declarative sync (install only what’s listed and delete everything else), you must run brew bundle cleanup --force. This command is state‑changing: it will uninstall any formula or cask not declared in the Brewfile. Use it with caution, preferably on a disposable machine or after backing up the list of installed packages:
# Dry‑run first to see what would be removed
brew bundle cleanup
# If the list looks correct, apply the changes
brew bundle cleanup --force
Running the cleanup without --force shows a preview, letting you verify that no essential tool is about to be removed.
Actionable closing
Start small: add a Brewfile to your existing dotfiles repo, run brew bundle dump to see what you have, then manually curate the file to keep only the tools you want to version‑control. Commit the file, and add a line to your onboarding script:
#!/usr/bin/env bash
set -e
brew bundle || { echo "Brewfile not satisfied"; exit 1; }
New hires can now clone the repo and run the script, getting a reproducible base environment in minutes instead of hours.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.