macOS dev environment without the Homebrew bloat
How I keep a fast, repeatable Mac dev setup without ending up with 600 packages I cannot account for.
- macOS
- Homebrew
- mise
- Developer Experience
A new Mac, set up the way most engineers set up Macs, will install around 600 Homebrew packages over its first six months. Around 30 of them are things you actually use. The other 570 are dependencies-of-dependencies, abandoned tools you tried once, and that one weird formula you installed because Stack Overflow told you to.
This is the setup I have settled on after running my dev environment as a real piece of infrastructure for years. The goal is not minimalism for the sake of it; it is knowing what is on the machine and why.
The hard rule: nothing system-wide that does not belong system-wide
System-wide installs are for things that are part of macOS’ job: the shell, fonts, GUIs, hardware drivers, video codecs. Languages and tools that change with the project should never be installed system-wide.
That means Homebrew is for GUIs and OS-level utilities:
brew install --cask raycast iterm2 1password orbstack rectangle
brew install git wget jq fd ripgrep htop direnv
That’s it. Around 25 packages. They almost never change.
Languages and tools: per-project, with mise
For every interpreter, runtime, or tool that ever has a “version that matters”, I use mise (the modern fork of asdf). Node, Python, Ruby, Deno, Go, Bun, Terraform, Helm, kubectl, awscli — all of them.
# .mise.toml in each project
[tools]
node = "22"
pnpm = "10"
python = "3.12"
mise install once, cd into the directory, and the right versions are on PATH. No nvm use, no pyenv shell, no manual juggling. When the project specifies, the project gets.
The difference vs Homebrew: when I delete a project, the tools it pinned are still on disk but they cost me nothing. When I mise prune, they are gone. Homebrew has no equivalent — once a formula is installed, removing it requires you to remember to remove it.
Containers: OrbStack, not Docker Desktop
OrbStack is a drop-in Docker Desktop replacement that does not eat 8 GB of RAM at idle. It also runs Linux VMs cheaply, which means I do not need a separate “I want a quick Ubuntu shell” tool. orb run ubuntu and you have one.
Docker Desktop has its place; on a Mac, that place is “I am being paid for it”. For personal and most professional use, OrbStack is the calm choice.
Shell: keep it boring, keep it portable
Zsh (the macOS default) plus a single config file:
# ~/.zshrc — 30 lines or fewer
eval "$(mise activate zsh)"
eval "$(direnv hook zsh)"
source <(starship init zsh) # if you want a fancy prompt
No oh-my-zsh, no plugin manager. Every line in .zshrc should justify itself. direnv is the unsung hero: per-directory env vars that load on cd (after you direnv allow once). Combined with mise, your project becomes self-describing: enter the folder, the right versions and the right env are loaded, exit the folder, your shell goes back to clean.
The actual machine setup, repeatable
Everything above lives in a Brewfile, a ~/.config/mise/config.toml, a ~/.zshrc, and a ~/.gitconfig. Four files, in a private dotfiles repo. New Mac:
- Install Xcode CLT, Homebrew.
brew bundle --file ./Brewfile.- Symlink the four configs.
mise installglobal tools.
Total time: 25 minutes. Total things I had to remember: zero.
The discipline pays off the day you actually do get a new Mac, or hand a setup script to a junior, or wipe the machine because something else got weird. The point of a clean setup is not to have a clean setup; it is to never lose a day to setup again.
Was this useful?