The macOS Setup Guide

Appendix B — Brewfile

brew bundle reads a Ruby-flavoured manifest and installs everything in it — formulae, casks, Mac App Store apps (via mas), and VS Code extensions. It is the single most useful file in a dotfiles repo: the whole software side of a Mac in 300 lines, reproducible with one command. Chapter 8 explains Homebrew itself; this appendix is the manifest.

sh
brew bundle --file=scripts/Brewfile          # install what's listed (skips what's already there)
brew bundle check --file=scripts/Brewfile    # what's missing?
brew bundle cleanup --file=scripts/Brewfile  # what's installed but NOT listed? (add --force to uninstall)
brew bundle dump --force --describe          # regenerate from what's installed → ./Brewfile

Conventions in this file#

  • Lines starting with #cask, #brew, #mas, #vscode (no space) are opt-in alternatives — remove the # to enable. Lines starting with # are commentary.
  • The uncommented set is the guide's default recommendation: a full developer CLI, mise + uv, container/cloud tooling, Nerd Fonts, Ghostty, VS Code, OrbStack, Firefox, and the Tier-1 daily-driver apps from chapter 19. On a fast connection it's ~15–25 minutes and ~8 GB (Xcode excluded).
  • restart_service: false on postgresql@18 and redis means they're installed but not started at login — brew services run postgresql@18 when you need them (chapter 15).
  • mas entries need you to be signed in to the App Store; the bootstrap script drops them otherwise. Find IDs with mas search "name".
  • vscode entries run code --install-extension; they're skipped if VS Code isn't installed.
  • Third-party taps (supabase/tap, nikitabobko/tap) will prompt for trust on first use under Homebrew 6 — that's the new tap-trust feature working as intended.

scripts/Brewfile#

ruby
# Brewfile — The macOS Setup Guide (Appendix B). September 2026.
# Install everything:   brew bundle --file=scripts/Brewfile
# Only the essentials:  comment out the "Optional" sections, or copy the lines you want into ~/.Brewfile.
# Keep it current:      brew bundle dump --force --describe --file=~/.Brewfile
# Lines starting with "# " are commentary; lines starting with "#brew"/"#cask" are opt-in — delete the "#" to enable.

tap "homebrew/services"          # brew services (launchd wrappers)
# tap "oven-sh/bun"              # example third-party tap — Homebrew 6 will prompt to trust it

# ─── Core CLI ─────────────────────────────────────────────────────────────────
brew "git"                        # newer than Apple's
brew "gh"                         # GitHub CLI
brew "git-delta"                  # better diffs
brew "git-lfs"
brew "lazygit"                    # TUI for git
brew "coreutils"                  # GNU utils as g-prefixed (gls, gdate…)
brew "gnu-sed"
brew "findutils"
brew "grep"
brew "bash"                       # bash 5 (macOS ships 3.2)
brew "zsh-autosuggestions"
brew "zsh-syntax-highlighting"
brew "zsh-completions"
brew "starship"                   # prompt
brew "fzf"                        # fuzzy finder (ctrl-r, ctrl-t)
brew "zoxide"                     # smarter cd
brew "eza"                        # ls replacement
brew "bat"                        # cat with syntax highlighting
brew "fd"                         # find replacement
brew "ripgrep"                    # grep replacement
brew "sd"                         # sed replacement for simple cases
brew "jq"                         # JSON
brew "yq"                         # YAML
brew "fx"                         # interactive JSON viewer
brew "tree"
brew "dust"                       # du replacement
brew "duf"                        # df replacement
brew "ncdu"
brew "procs"                      # ps replacement
brew "bottom"                     # top replacement (btm)
brew "htop"
brew "hyperfine"                  # benchmarking
brew "tldr"                       # simplified man pages (tlrc)
brew "tmux"
brew "zellij"                     # tmux alternative
brew "neovim"
brew "watch"
brew "wget"
brew "curl"
brew "httpie"
brew "xh"                         # httpie in Rust
brew "openssh"
brew "mosh"
brew "rsync"
brew "rclone"
brew "p7zip"
brew "unar"
brew "trash"                      # rm → Trash
brew "mas"                        # Mac App Store CLI
brew "duti"                       # set default apps
brew "terminal-notifier"
brew "ical-buddy"
brew "switchaudio-osx"            # switch audio devices from CLI
brew "pandoc"
brew "shellcheck"
brew "shfmt"
brew "direnv"
brew "age"                        # modern file encryption
brew "sops"
brew "gnupg"
brew "pinentry-mac"
brew "ykman"                      # YubiKey manager
brew "nmap"
brew "mtr"
brew "iperf3"
brew "dog"                        # dig replacement (or: brew "doggo")
brew "wireguard-tools"
brew "ffmpeg"
brew "imagemagick"
brew "exiftool"
brew "pngquant"
brew "oxipng"
brew "graphviz"
brew "poppler"                    # pdftotext etc.
brew "qpdf"
brew "sqlite"
brew "whisper-cpp"

# ─── Runtimes & language tooling ──────────────────────────────────────────────
brew "mise"                       # runtime versions (node, python, java, go, rust, ruby…)
brew "uv"                         # Python packaging
brew "ruff"
brew "pipx"                       # optional if you use uv tool
brew "pnpm"                       # or via corepack/mise
brew "cmake"
brew "ninja"
brew "llvm"                       # newer clang/clangd/lldb than CLT (optional; big)
brew "gcc"                        # real GCC for coursework (gcc-15)
brew "make"                       # GNU make 4 as gmake
brew "bear"                       # compile_commands.json for clangd
brew "ccache"
brew "typst"
brew "tectonic"                   # self-contained LaTeX (or cask mactex-no-gui)
brew "protobuf"
brew "grpcurl"
brew "sqlfluff"
brew "postgresql@18", restart_service: false
brew "redis", restart_service: false
brew "pgcli"
brew "mycli"
brew "usql"                       # universal SQL CLI
brew "duckdb"

# ─── Containers, cloud, DevOps ────────────────────────────────────────────────
brew "docker"                     # CLI only; runtime = OrbStack (cask below) or Docker Desktop
brew "docker-compose"
brew "docker-buildx"
brew "dive"                       # explore image layers
brew "lazydocker"
brew "kubectl"
brew "kubectx"
brew "k9s"
brew "helm"
brew "kind"
brew "stern"
brew "awscli"
brew "aws-vault"
brew "azure-cli"
brew "opentofu"                   # or "terraform"
brew "ansible"
brew "cloudflared"
brew "wrangler"
brew "flyctl"
brew "vercel-cli"
brew "supabase/tap/supabase"
brew "ngrok"
brew "tailscale"                  # CLI (cask "tailscale-app" for the menu bar app)
brew "act"                        # run GitHub Actions locally
brew "actionlint"
brew "pre-commit"
brew "lefthook"
brew "just"                       # command runner
brew "watchman"
brew "mkcert"                     # local HTTPS certs
brew "caddy"
brew "step"
brew "trivy"                      # vuln scanner
brew "gitleaks"
brew "cosign"

# ─── Fonts ────────────────────────────────────────────────────────────────────
cask "font-jetbrains-mono-nerd-font"
cask "font-fira-code-nerd-font"
cask "font-meslo-lg-nerd-font"
cask "font-inter"
cask "font-ibm-plex"
cask "font-monaspace"
#cask "font-cascadia-code-nf"
#cask "font-iosevka-nerd-font"

# ─── Terminal, editors, dev apps ──────────────────────────────────────────────
cask "ghostty"
#cask "iterm2"
#cask "wezterm"
#cask "warp"
cask "visual-studio-code"
#cask "cursor"
#cask "zed"
#cask "jetbrains-toolbox"
#cask "sublime-text"
cask "orbstack"                   # containers + Linux VMs; replaces Docker Desktop
#cask "docker-desktop"
#cask "podman-desktop"
cask "utm"                        # VMs (QEMU); free
#cask "vmware-fusion"
#cask "parallels"
cask "tableplus"
#cask "postico"
#cask "dbeaver-community"
#cask "pgadmin4"
#cask "mongodb-compass"
cask "bruno"                      # API client (Postman alternative)
#cask "postman"
#cask "insomnia"
#cask "proxyman"
#cask "charles"
cask "fork"                       # Git GUI (free tier)
#cask "sublime-merge"
#cask "tower"
#cask "github"
#cask "kaleidoscope"
cask "dash"                       # offline docs (paid)
#cask "devdocs"
#cask "xcodes"                    # manage Xcode versions
#cask "android-studio"
#cask "figma"
#cask "sf-symbols"
#cask "lm-studio"
#cask "ollama-app"
#cask "wireshark-app"
#cask "gpg-suite"
#cask "yubico-authenticator"
#cask "launchcontrol"
#cask "suspicious-package"
#cask "macfuse"

# ─── Browsers ─────────────────────────────────────────────────────────────────
cask "firefox"
#cask "google-chrome"
#cask "brave-browser"
#cask "zen"
#cask "vivaldi"
#cask "orion"
#cask "chromium"

# ─── Daily-driver essentials (Tier 1, all free) ──────────────────────────────
cask "raycast"                    # launcher; or "alfred"
cask "rectangle"                  # window snapping; or "aerospace" below
#cask "nikitabobko/tap/aerospace"
cask "maccy"                      # clipboard history (skip if using Raycast's)
cask "jordanbaird-ice"            # menu bar icon manager
cask "stats"                      # system stats in menu bar
cask "itsycal"                    # menu bar calendar
cask "iina"                       # video player
cask "shottr"                     # screenshots + OCR
cask "appcleaner"                 # or "pearcleaner"
cask "the-unarchiver"
cask "karabiner-elements"         # key remapping
cask "hammerspoon"                # Lua automation
cask "bitwarden"                  # or "1password" + "1password-cli"
cask "obsidian"
cask "zotero"
cask "keka"
cask "monitorcontrol"
cask "linearmouse"
cask "latest"                     # app update checker
cask "qlmarkdown"                 # Quick Look plugins ↓
cask "syntax-highlight"
cask "quicklook-json"
cask "qlvideo"

# ─── Optional daily-driver apps ───────────────────────────────────────────────
#cask "1password"
#cask "1password-cli"
#cask "keepassxc"
#cask "alfred"
#cask "aldente"
#cask "betterdisplay"
#cask "lulu"
#cask "notion"
#cask "logseq"
#cask "anytype"
#cask "typora"
#cask "skim"
#cask "sioyek"
#cask "anki"
#cask "netnewswire"
#cask "syncthing-app"
#cask "maestral"
#cask "google-drive"
#cask "dropbox"
#cask "onedrive"
#cask "tailscale-app"
#cask "cyberduck"
#cask "transmit"
#cask "handbrake-app"
#cask "kap"
#cask "obs"
#cask "audacity"
#cask "imageoptim"
#cask "pixelmator-pro"           # via App Store (mas) instead
#cask "affinity"
#cask "drawio"
#cask "excalidraw"
#cask "libreoffice"
#cask "microsoft-office"
#cask "mactex-no-gui"
#cask "texshop"
#cask "miniforge"
#cask "r"
#cask "rstudio"
#cask "positron"
#cask "racket"
#cask "logisim-evolution"
#cask "mathpix-snipping-tool"
#cask "steam"
#cask "whisky"
#cask "crossover"
#cask "espanso"
#cask "keyboard-maestro"
#cask "bettertouchtool"
#cask "hazel"
#cask "numi"
#cask "rocket"
#cask "keyboardcleantool"
#cask "coconutbattery"
#cask "grandperspective"
#cask "daisydisk"
#cask "omnidisksweeper"
#cask "macs-fan-control"
#cask "istat-menus"
#cask "cleanshot"
#cask "screen-studio"
#cask "fantastical"
#cask "busycal"
#cask "mimestream"
#cask "thunderbird"
#cask "spotify"
#cask "slack"
#cask "discord"
#cask "zoom"
#cask "microsoft-teams"
#cask "signal"
#cask "telegram"
#cask "whatsapp"
#cask "backblaze"
#cask "arq"
#cask "carbon-copy-cloner"
#cask "little-snitch"
#cask "malwarebytes"

# ─── Mac App Store (needs: mas + signed into the App Store) ──────────────────
# Find IDs with: mas search "name"
mas "Xcode", id: 497799835                   # 12+ GB; only if doing Apple-platform dev
#mas "Amphetamine", id: 937984704
#mas "Things 3", id: 904280696
#mas "Pixelmator Pro", id: 1289583905
#mas "Bear", id: 1091189122
#mas "Wipr 2", id: 1662217862               # Safari content blocker
#mas "AdGuard for Safari", id: 1440147259
#mas "Refined GitHub", id: 1519867270       # Safari extension
#mas "Keynote", id: 409183694
#mas "Pages", id: 409201541
#mas "Numbers", id: 409203825
#mas "Apple Configurator", id: 1037126344   # DFU revive
#mas "TestFlight", id: 899247664
#mas "Kindle", id: 302584613
#mas "WhatsApp Messenger", id: 310633997
#mas "Slack for Desktop", id: 803453959

# ─── VS Code extensions (brew bundle installs these via `code --install-extension`) ──
vscode "editorconfig.editorconfig"
vscode "esbenp.prettier-vscode"
vscode "biomejs.biome"
vscode "dbaeumer.vscode-eslint"
vscode "charliermarsh.ruff"
vscode "ms-python.python"
vscode "ms-python.vscode-pylance"
vscode "ms-toolsai.jupyter"
vscode "golang.go"
vscode "rust-lang.rust-analyzer"
vscode "vadimcn.vscode-lldb"
vscode "llvm-vs-code-extensions.vscode-clangd"
vscode "redhat.java"
vscode "vscjava.vscode-java-pack"
vscode "ms-azuretools.vscode-containers"
vscode "ms-vscode-remote.remote-ssh"
vscode "ms-vscode-remote.remote-containers"
vscode "github.copilot"
vscode "github.copilot-chat"
vscode "github.vscode-pull-request-github"
vscode "github.vscode-github-actions"
vscode "eamodio.gitlens"
vscode "mhutchie.git-graph"
vscode "usernamehw.errorlens"
vscode "streetsidesoftware.code-spell-checker"
vscode "yzhang.markdown-all-in-one"
vscode "bierner.markdown-mermaid"
vscode "james-yu.latex-workshop"
vscode "myriad-dreamin.tinymist"
vscode "tamasfe.even-better-toml"
vscode "redhat.vscode-yaml"
vscode "timonwong.shellcheck"
vscode "foxundermoon.shell-format"
vscode "mkhl.direnv"
vscode "hashicorp.terraform"
vscode "ms-kubernetes-tools.vscode-kubernetes-tools"
vscode "pkief.material-icon-theme"
vscode "github.github-vscode-theme"
#vscode "vscodevim.vim"
#vscode "asvetliakov.vscode-neovim"
#vscode "anthropic.claude-code"
#vscode "continue.continue"

Per-machine Brewfiles#

One file rarely fits a laptop, a desktop, and a work machine. Two patterns:

Includes — a base file plus a per-host file, concatenated at install time:

sh
# ~/.dotfiles/install.sh
cat Brewfile.base "Brewfile.$(scutil --get ComputerName | tr ' ' '-')" 2>/dev/null > /tmp/Brewfile
brew bundle --file=/tmp/Brewfile

Ruby conditionals — the Brewfile is Ruby, so this works:

ruby
host = `scutil --get ComputerName`.strip
cask "docker-desktop"  if host == "work-mbp"       # company mandates Docker Desktop
cask "orbstack"        unless host == "work-mbp"
cask "steam"           if host == "studio"
brew "postgresql@18", restart_service: (host == "studio")

Keeping it honest#

Every month (the routine in chapter 18):

sh
brew bundle cleanup --file=~/.dotfiles/Brewfile    # lists things you installed ad hoc
# → either add them to the Brewfile (you use them) or `brew uninstall --zap` them (you don't)
brew bundle dump --force --describe --file=/tmp/Brewfile.now && diff ~/.dotfiles/Brewfile /tmp/Brewfile.now

brew bundle dump sorts alphabetically and loses your comments, so don't overwrite a hand-curated file with it — diff and copy the new lines across. --describe adds each formula's description as a comment, which makes a dumped file readable enough to start from.

Formula and cask name changes#

Homebrew renames casks when upstream renames apps (docker → docker-desktop, tailscale → tailscale-app, handbrake → handbrake-app, wireshark → wireshark-app all happened in 2025–26). brew bundle prints a deprecation warning with the new name; update the file. brew search --cask name finds the current one.

View this chapter as Markdown · Edit on GitHub

Last reviewed September 2026. Written for macOS 26 Tahoe and macOS 27 Golden Gate on Apple silicon Macs. Prices are US list prices. Text is CC BY 4.0; code and configs are MIT.