Brewfile Management — Reproduce a Mac Dev Environment as Code with `brew bundle`
A Brewfile pattern that pins your Mac setup into a single file. dump → cleanup → cleanup → install on another machine, one full cycle.
Typing brew install line by line every time you set up a new Mac is inefficient. brew bundle lets a single Brewfile bundle brew formulae, casks, VS Code extensions, and even App Store apps into something reproducible. Once you internalize the pattern, machine swaps and teammate onboarding shrink by 30‑minute increments.
This guide targets macOS 14+ / Homebrew 4.x. It is the freeze-your-environment-as-code step after Mac initial setup.
TL;DR
- Use
brew bundle dump --describeto snapshot the current state into a Brewfile - Hand‑edit (remove duplicates and test packages) → commit to Git
- On a new Mac:
brew bundle install --file=./Brewfile→ done - Periodically
brew bundle cleanupto drop anything not in the Brewfile
Prerequisites
- Homebrew 4.x installed — see Mac initial setup
- (Optional) A GitHub repo or Gist (to store the Brewfile)
- (Optional)
mas-cli—brew install masif you want to bundle Mac App Store apps too
Download an Example Brewfile
The devAlice recommended baseline — grab it and edit to suit your environment.
Brewfile# 1. Download
curl -fsSL https://devalice.jaceclub.com/assets/mac/brewfile-management/Brewfile.example -o Brewfile
# 2. Verify SHA-256
shasum -a 256 Brewfile
# Expected: 2b54214d29391d15666fe5e15768aa2e6e6eabea8b3c772989d6e1216a816c5f
# 3. Inspect (review which packages are in it; edit to fit your environment)
less Brewfile
# 4. Apply
brew bundle install --file=./Brewfile1. Brewfile Syntax Basics — 5 min
It's Ruby DSL under the hood, but you really only need five keywords.
# Brewfile
# tap — extra repository
tap "homebrew/bundle"
tap "homebrew/services"
# brew — formulae (CLI tools)
brew "git"
brew "gh"
brew "mise"
# Optional arguments are allowed
brew "postgresql@16", restart_service: :changed
brew "redis", start_service: true
# cask — GUI apps
cask "visual-studio-code"
cask "rectangle"
# Mac App Store (requires mas-cli)
mas "Xcode", id: 497799835
# VS Code extensions (similar syntax to mas)
vscode "dbaeumer.vscode-eslint"
vscode "esbenp.prettier-vscode"Comments + Sectioning
# ─── CLI ──────────────────────────────────────
brew "git"
brew "gh"
# ─── GUI ──────────────────────────────────────
cask "visual-studio-code"Section separators pay back hugely in maintenance — diffs become meaningful after each commit.
2. Generate a Brewfile from Current State — dump — 3 min
If you already have a well-set-up machine, snapshot it in one shot.
# Create a Brewfile in the current directory (--force overwrites)
brew bundle dump --force --describe
# --describe adds a # comment to every line — readability ↑Sample output:
# ./Brewfile (auto-generated)
tap "homebrew/services"
brew "git"
# Distributed revision control system
brew "gh"
# GitHub command-line tool
brew "jq"
# Lightweight and flexible command-line JSON processor
brew "mise"
# Polyglot runtime manager (asdf rust clone)
cask "visual-studio-code"
# Open-source code editorClean Up the Dump by Hand
A fresh dump captures everything currently installed, which usually has noise. Cleanup checklist:
- Test-only packages you no longer use → remove
- Language runtimes → drop them if you'll manage them with
mise(e.g.,node,python@3.x) - Work vs personal → split into
Brewfile.work/Brewfile.personal - Add section comments
A 10-minute investment now saves an hour of future setup.
3. Apply on Another Machine — install — 10–30 min
In the directory containing your Brewfile, on the new machine (or after cleanup):
brew bundle install --file=./Brewfile
# Or use an absolute path
brew bundle install --file=$HOME/dotfiles/Brewfile
# Already-installed items are skipped (idempotent)The first run is dominated by cask downloads (~50–200MB each). Plan for 10–30 minutes.
Step-by-Step View — --verbose
brew bundle install --file=./Brewfile --verboseFailures Only — --quiet
brew bundle install --file=./Brewfile --quiet4. Drop Packages Not in the Brewfile — cleanup — 5 min
To make your Brewfile the single source of truth, remove anything not listed.
# Dry run — see what would be removed
brew bundle cleanup --file=./Brewfile
# Actually remove — be deliberate
brew bundle cleanup --file=./Brewfile --force⚠️
--forceuninstalls immediately. Only run it after your Brewfile is fully cleaned; otherwise you might need to reinstall.
5. Split Strategy — work / personal / experiment
A single monolithic Brewfile eventually bloats. Split by context.
~/dotfiles/
├── Brewfile # Common to all (CLI essentials)
├── Brewfile.work # Work-only (Slack, Zoom, ...)
├── Brewfile.personal # Personal (game launchers, ...)
└── Brewfile.optional # Occasionally used
Apply Them Together
cd ~/dotfiles
brew bundle install --file=./Brewfile
brew bundle install --file=./Brewfile.work
brew bundle install --file=./Brewfile.personalOr via Makefile/shell script
# ~/dotfiles/bundle.sh
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
for f in Brewfile Brewfile.work Brewfile.personal; do
[ -f "$f" ] && brew bundle install --file="./$f"
done
echo "✓ All Brewfiles applied"6. Version Control with Git — 3 min
A Brewfile is a core file in any dotfiles repo.
cd ~/dotfiles
git init
git add Brewfile Brewfile.*
git commit -m "feat: initial Brewfile baseline"
git remote add origin git@github.com:<user>/dotfiles.git
git push -u origin mainWhen adding/removing packages later:
brew bundle dump --force --describe # refresh
git diff Brewfile # review changes
git commit -am "feat: add postgresql + redis"
git pushOn a new machine:
git clone git@github.com:<user>/dotfiles.git ~/dotfiles
cd ~/dotfiles
brew bundle install --file=./Brewfile7. Automation — brew services + macOS LaunchAgent
Auto-dump once a day so your brew state doesn't drift.
~/Library/LaunchAgents/dev.local.brewdump.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>dev.local.brewdump</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-lc</string>
<string>cd $HOME/dotfiles && /opt/homebrew/bin/brew bundle dump --force --describe</string>
</array>
<key>StartCalendarInterval</key>
<dict><key>Hour</key><integer>22</integer><key>Minute</key><integer>0</integer></dict>
</dict>
</plist>launchctl load ~/Library/LaunchAgents/dev.local.brewdump.plistAuto-dumps at 22:00 every day. Each morning, git diff shows what changed overnight.
8. Troubleshooting
"Error: Cask 'X' is not installed" (during cleanup)
The cask was already removed elsewhere. Run brew bundle cleanup --force again to reconcile.
Dump captures too many unintended packages
Old transitive dependencies showing up as top-level. Tidy them up:
brew autoremove # remove leaf packages only used as deps
brew bundle dump --force --describe # re-dumpApp Store apps don't appear in the dump
mas-cli is missing. brew install mas and re-dump.
Some casks fail SHA-256 during install
Happens right after Homebrew updates cask definitions. brew update and retry.
Next Steps
- Mac initial setup — Homebrew/SSH setup
- Mac dotfiles — shell/git settings to bundle alongside your Brewfile
- Mac dev toolchain — language runtimes via mise
References
brew bundle(official)- Homebrew Cask
mas-cli— Mac App Store CLI
Changelog
- 2026-05-12 — Initial draft (devAlice M2 seed expansion)