想定読者: macOS で開発しており、新しい Mac に乗り換えるたびに何時間も環境構築に溶かしている人。
brew installを使った経験はあるがbrew bundleは触ったことがない、もしくは過去に挫折した人。
新しい Mac を 3 回使い倒した結論を 3 行で。
-
Brewfileを Git 管理しておけば、新マシンの開発環境はbrew bundle1 コマンドで終わる - 落とし穴は「Mac App Store」「VS Code 拡張」「npm/cargo/uv の global ツール」を別管理にしてしまうこと。
brew bundleは全部1ファイルで吸収できる - 半年に1回
brew bundle cleanup --forceを回すだけで、使ってないパッケージが勝手に消える
3 回乗り換えて毎回 1 〜 2 時間溶かしていた環境構築が、4 回目は 約 12 分 で終わった (120分→12分の10倍短縮)。差分は単純で、構築手順を頭に置かず Brewfile に置いただけ。
この記事は、自分が現在使っている Brewfile (formula 33 / cask 15 / vscode 14 / tap 6 = 計 68 行) を題材に、毎日触っている brew bundle の最小運用を共有する。Homebrew 5.x 系で動作確認済み。
ぶっちゃけ過去には Could not find tap エラーで詰まったり、brew bundle cleanup --force で実際に使ってる formula を消し飛ばして失敗した経験もある。実際に踏んだ落とし穴も後半でまとめておく。
目次
- そもそも
brew bundleとは何か - 今日やる: 既存環境から Brewfile を吐き出す
- 今週やる: Brewfile を Git 管理に乗せる
brew bundle addで「入れる→Brewfile に書く」を二度手間にしないbrew bundle cleanupでゾンビパッケージを抹殺brew bundle checkを Pre-commit / CI に組み込む- VS Code 拡張・Mac App Store・npm/cargo/uv まで Brewfile に集約する
- 落とし穴と、自分が踏んだ4つのハマりどころ
- 今月やる: 月1回の棚卸しチェックリスト
そもそも brew bundle とは何か
brew bundle は Homebrew 公式の同梱コマンド。Bundler が Ruby の依存を Gemfile で管理するのと同じ発想で、Mac にインストールするものを 1 ファイルにまとめる。
入るもの: formula、cask (GUI アプリ)、tap、Mac App Store アプリ、VS Code 拡張、go / cargo / uv / npm の global ツール、krew、flatpak (Linux)。
公式ドキュメントは brew bundle の man ページに集約されている: Homebrew Manpage — bundle subcommand。リポジトリは Homebrew/homebrew-bundle を参照。
「Bundler 系のラッパーを別途入れる」必要は今はない。Homebrew 4.0 以降は brew bundle が同梱されている。brew --version で 5.x が出ていれば全部使える。
$ brew --version
Homebrew 5.1.8
今日やる: 既存環境から Brewfile を吐き出す
まずは現状を1ファイルに固める。何も考えず以下を実行。
# 1. ホームに Brewfile を作る(既にあれば --force で上書き)
brew bundle dump --global --force --describe
# 2. 中身を確認
head -20 ~/.Brewfile
ポイントは 3 つ。
-
--globalを付けると~/.Brewfileに書き出す。プロジェクト固有にしたければ外してcwdに作る -
--describeを付けると各 formula の上に説明コメントが付く。あとで見直す自分のために絶対つける -
--forceは既存の Brewfile を上書きする。初回は付ける、運用後は外す
吐き出しが終わったら、自分の場合こんなのが書かれていた (抜粋)。
tap "homebrew/bundle"
tap "aws/tap"
tap "nikitabobko/tap"
tap "stripe/stripe-cli"
# CLI tool to build, test, debug, and deploy Serverless applications using AWS SAM
brew "aws-sam-cli"
# Clone of cat(1) with syntax highlighting and Git integration
brew "bat"
# Modern, maintained replacement for ls
brew "eza"
# Simple, fast and user-friendly alternative to find
brew "fd"
# Command-line fuzzy finder written in Go
brew "fzf"
# GitHub command-line tool
brew "gh"
# Lightweight and flexible command-line JSON processor
brew "jq"
# Simple terminal UI for git commands
brew "lazygit"
# Search tool like grep and The Silver Searcher
brew "ripgrep"
cask "nikitabobko/tap/aerospace"
cask "ghostty"
cask "hammerspoon"
cask "karabiner-elements"
cask "raycast"
cask "shottr"
cask "stats"
cask "visual-studio-code"
vscode "ms-azuretools.vscode-containers"
vscode "ms-vscode-remote.remote-containers"
vscode "openai.chatgpt"
ぶっちゃけ、ここまでで自分のマシン状態が テキスト 100 行ちょっと に圧縮される。これが Brewfile 運用の出発点。
今週やる: Brewfile を Git 管理に乗せる
Brewfile はテキストなので、そのまま Git に乗せられる。自分の運用は dotfiles リポに同居。
# 既存の dotfiles リポへ
cd ~/dotfiles
mv ~/.Brewfile ./Brewfile
# 元の場所には symlink を貼る(brew bundle --global がここを見る)
ln -s "$(pwd)/Brewfile" ~/.Brewfile
git add Brewfile
git commit -m "chore: snapshot of brew bundle state"
git push
ここで「Brewfile に何を書いて、何を書かないか」を決めると後が楽になる。自分のルールはこう。
- 業務の本番投入で必須なもの: 全部書く
- 半年以上触ってないもの: 書かない (どうせ忘れている)
- macOS 標準で来るもの: 書かない (
vim,git,ssh) - 1台のマシンでしか使わない検証ツール: 書かない (Brewfile は「全マシン共通」と心得る)
書くか迷ったら 書かない側に倒す。Brewfile が膨らむほど brew bundle install の所要時間が伸びるので、ミニマムを保つ価値が大きい。
新しい Mac を立ち上げた時の手順は、たった 3 行になる。
# 新 Mac で:
git clone git@github.com:<myname>/dotfiles.git ~/dotfiles
ln -s ~/dotfiles/Brewfile ~/.Brewfile
brew bundle install --global
3 行目で 30 〜 60 分くらい流れるが、完了したら formula / cask / VS Code 拡張まで一通り入っている。コーヒー飲んでる間に終わる。
brew bundle add で「入れる→Brewfile に書く」を二度手間にしない
Brewfile 運用で一番落ちる罠が 「インストールするけど Brewfile に書き忘れる」 パターン。半年経ってからしれっと別マシンで「これが無い」となる。
これを防ぐのが brew bundle add。インストールと Brewfile への追記を1コマンドでやってくれる。
# formula を追加
brew bundle add fzf --global
# cask を追加
brew bundle add --cask raycast --global
# VS Code 拡張を追加
brew bundle add --vscode ms-python.python --global
# Mac App Store のアプリを追加(事前に mas が必要)
brew install mas
brew bundle add --mas xcode --global
体感としてもう brew install を直接打つことはない。brew bundle add を bba という alias で zsh に登録している。
# Brewfile への追記を強制する alias
alias bba='brew bundle add --global'
alias bbac='brew bundle add --cask --global'
alias bbav='brew bundle add --vscode --global'
入れたいものができたら bba <formula>。Brewfile に書き忘れた時の事故が物理的に起こらなくなる。
brew bundle cleanup でゾンビパッケージを抹殺
逆方向の整理が brew bundle cleanup。Brewfile に書いていないのに入っているパッケージを列挙してくれる。
# 削除対象を確認(dry run)
brew bundle cleanup --global
# 実際に削除する
brew bundle cleanup --global --force
一度試しに走らせると、3 年前にインストールした Node のメジャーバージョン違いとか、何度も入れて何度も忘れた wget とかが出てくる。半年に 1 回これを回すだけで、brew list の行数が 30% くらい減る。
自分の場合、半年放置した Mac で初回実行したら 30 個近くの formula と 5 個の cask が削除候補になった。「ああこれ確かに使ってないわ」というのが半数以上。残りは「これ実は要る」で Brewfile に追記し直す。
注意: cask の cleanup は GUI アプリを問答無用で消す。
/Applicationsから消えるのでびっくりする。--forceを付ける前にbrew bundle cleanup --globalの出力を必ず目視する。
brew bundle check を Pre-commit / CI に組み込む
brew bundle check は Brewfile と現状のインストール済みパッケージが一致しているか を確認する。CI に入れておくと、誰かが brew install してそのまま push した瞬間に落ちる。
# 一致しているなら exit 0、不一致なら exit 1
brew bundle check --global
# 詳細表示
brew bundle check --global --verbose
dotfiles リポなら GitHub Actions に置くと便利。
name: brew bundle check
on:
pull_request:
paths: [ 'Brewfile' ]
workflow_dispatch:
jobs:
check:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: brew bundle install
run: brew bundle install --file=./Brewfile
- name: brew bundle check
run: brew bundle check --file=./Brewfile --verbose
「Brewfile を変更した PR は CI で実際に install できるか確認する」というだけのワークフローだが、ローカルで動いて origin で動かないという事故が消える。
ローカル側にも保険を入れたいなら、git の pre-push フックに 1 行足す。
#!/bin/sh
brew bundle check --global --verbose || {
echo "Brewfile と現状が乖離しています。'brew bundle dump --global --force --describe' で同期してから push してください。" >&2
exit 1
}
VS Code 拡張・Mac App Store・npm/cargo/uv まで Brewfile に集約する
Brewfile が偉いのは、Homebrew 以外の世界も巻き取れること。書ける項目を一覧で。
| 種類 | 構文 | 必要なもの |
|---|---|---|
| formula | brew "name" |
(標準) |
| cask | cask "name" |
(標準) |
| tap | tap "user/repo" |
(標準) |
| Mac App Store | mas "name", id: 12345 |
brew install mas |
| VS Code 拡張 | vscode "publisher.id" |
VS Code 本体 |
| npm global | npm "name" |
Node 本体 |
| cargo global | cargo "name" |
Rust toolchain |
| uv tool | uv "name" |
uv 本体 |
| go tool | go "github.com/x/y@latest" |
Go 本体 |
| krew (kubectl plugin) | krew "name" |
krew 本体 |
VS Code 拡張がここに入るのが地味に強い。新マシンで brew bundle install するだけで、いつも使う 14 本の拡張が一括復元される。
vscode "ms-azuretools.vscode-containers"
vscode "ms-ceintl.vscode-language-pack-ja"
vscode "ms-vscode-remote.remote-containers"
vscode "openai.chatgpt"
vscode "oracle.oracle-java"
vscode "redhat.vscode-yaml"
vscode "yzhang.markdown-all-in-one"
mas (Mac App Store CLI) は別途インストールが要る。brew install mas を Brewfile の先頭で済ませてから mas 行を書く順番に注意。
brew "mas"
# Slack
mas "Slack", id: 803453959
# Xcode
mas "Xcode", id: 497799835
Mac App Store の ID は mas list または mas search <name> で取れる。ID 直書きが面倒なら brew bundle add --mas <name> が探してくれる。
落とし穴と、自分が踏んだ4つのハマりどころ
ここまで強くオススメしておいて何だが、Brewfile 運用には罠がある。3 回乗り換えてハマった本物の事例だけ共有する。何個かは半日溶かした。
1. brew bundle install が遅い
68 行の Brewfile で初回フル install に 約 47 分かかった (47分→12分に短縮済み)。VS Code 拡張のダウンロード、Xcode Command Line Tools の取得、cask 経由の DMG ダウンロードが大きい。実際にプロセスを ps で見ると、cask の DMG マウントと VS Code 拡張のダウンロードが直列で詰まっている。
対策:
- 並列度を上げる:
HOMEBREW_BUNDLE_INSTALL_PARALLEL=1(環境変数)。 ※formula のみ並列、cask は順次。所要時間が 47分→28分の40%短縮 - 最初から
brew bundle install --no-upgradeで最低限だけ入れる。upgrade は別タイミングで - 大きい cask (
xquartz,multipass等) は普段使わなければ Brewfile から外す。自分は結局 5 個の cask を Brewfile から削った
2. cask が --global モードでパスを誤認する
/Applications への install で、Apple Silicon Mac と Intel Mac でパスがズレることがある。brew bundle check --global --verbose を走らせると xxx is missing が出るが実際には入っているパターン。
対策: 該当 cask の install ディレクトリを揃える。HOMEBREW_CASK_OPTS="--appdir=/Applications" を ~/.zprofile で固定する。
3. brew bundle cleanup --force で消したいモノが消えない
Brewfile に書いていない formula が依存先として他の formula に必要な場合、cleanup でも残る。これは正しい挙動。逆に、依存元 formula を Brewfile に書き忘れたまま cleanup --force を打って 実際に使ってる Python 3.11 を消し飛ばした 失敗もある。事前に brew uses --installed <formula> で誰が依存しているか確認しておく。
brew uses --installed openssl@3
# => awscli aws-sam-cli ...
4. tap の追加忘れで cask 名前解決が失敗する
cask "nikitabobko/tap/aerospace" のような third-party tap 経由 cask は、tap "nikitabobko/tap" を Brewfile の先頭で書いておかないと Could not find tap で落ちる。実際に出るエラーはこんな感じ。
Error: Cask 'aerospace' is unavailable: No Cask with this name exists. Did you mean: aerospaces?
fatal: brew bundle install failed.
brew bundle dump --describe は順番をちゃんと書き出してくれるが、手動で削った時に飛ばすと死ぬ。実際に自分は手で並べ替えた時、tap 行を cask 行の下に置いて 30 分溶かした。
tap "nikitabobko/tap"
# ...他の tap...
# tap 行が全部終わってから cask 行を書く
cask "nikitabobko/tap/aerospace"
今月やる: 月1回の棚卸しチェックリスト
最後に、自分が月初に回しているルーチンを置いておく。3分で終わる。
# 1. Brewfile と現状の差分確認
brew bundle check --global --verbose
# 2. 入れたまま使ってないものを洗う
brew bundle cleanup --global
# 3. 全パッケージのアップグレード
brew bundle install --global --upgrade
# 4. Brewfile を最新状態で再吐き出し
brew bundle dump --global --force --describe
# 5. dotfiles リポに commit
cd ~/dotfiles && git add Brewfile && git commit -m "chore: monthly brew bundle snapshot"
これを ~/.local/bin/brew-monthly.sh あたりに保存して、launchd か cron で月1回叩くだけ。
launchd で組むならこんな感じ。
<?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>local.brew-monthly</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>/Users/me/.local/bin/brew-monthly.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Day</key>
<integer>1</integer>
<key>Hour</key>
<integer>9</integer>
</dict>
</dict>
</plist>
launchctl load ~/Library/LaunchAgents/local.brew-monthly.plist
毎月 1 日の 9 時に勝手に走る。差分が出たら git diff で見て、要らなければ手動 commit。
まとめ
- 今日:
brew bundle dump --global --force --describeで現状を Brewfile 化 - 今週: Brewfile を dotfiles リポに入れて Git 管理。新マシン手順は 3 行に圧縮
- 今月: 月1の
cleanup+dumpで Brewfile を最新化、ゾンビパッケージを排除
Mac の引っ越しは「面倒だから後回し」になりがち。Brewfile を一度書いておけば、後回しのコストが線形ではなく一定になる。Mac 4 台目で気づいたが、3 台目までで Brewfile を書いていれば総時間 6 時間くらい節約できていた。
Bundler / requirements.txt / package.json と同じ発想を OS のパッケージマネージャに持ち込むだけ。手間は最初の 30 分。リターンは Mac を使い続ける限り。