A Clean Mac Developer Setup
15 Apr 2026A fresh Mac is faster to set up right the first time than to fix six months in once you’ve accumulated three package managers and no idea which one owns what. Here’s the order that avoids the common conflicts.
1. Xcode Command Line Tools, before anything else
xcode-select --install
This gets you git, clang, and the headers most compiled dependencies need. Homebrew and most language installers assume this is already present and fail in confusing ways if it isn’t.
2. Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
On Apple Silicon it installs to /opt/homebrew, not /usr/local. Add it to your PATH — the installer prints the exact line, usually:
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
Skip this step and brew-installed tools silently won’t be found by your shell, which looks like a broken install when it’s actually a missing PATH entry.
3. Pick one version manager per language, not three
The most common mess on a dev machine is Node installed via Homebrew, nvm, and the official installer, all fighting over which node runs. Pick one per language:
| Language | Recommended |
|---|---|
| Node | mise or nvm — not brew install node |
| Python | mise or pyenv — not system Python, not brew install python |
| Ruby | mise or rbenv |
mise (formerly rtx) handles all three from one tool and one config file (.tool-versions or mise.toml) per project, which is the actual reason to prefer it over juggling nvm + pyenv + rbenv separately:
brew install mise
echo 'eval "$(mise activate zsh)"' >> ~/.zshrc
mise use --global node@lts python@3.12
4. Git config, before you clone anything
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main
git config --global pull.rebase false
Set pull.rebase explicitly — the unset default prints a warning on every pull with a fast-forward-able history, and it’s one less thing to see and ignore a hundred times.
5. SSH key for git remotes
ssh-keygen -t ed25519 -C "you@example.com"
eval "$(ssh-agent -s)"
ssh-add --apple-use-keychain ~/.ssh/id_ed25519
--apple-use-keychain stores the passphrase in macOS Keychain so you’re not retyping it every terminal session. Add the public key (~/.ssh/id_ed25519.pub) to GitHub/GitLab before your first clone.
6. Terminal and shell
Default Terminal.app works. If you’re going to spend eight hours a day in it, iTerm2 or Ghostty (both brew install --cask) add real scrollback search and split panes that the built-in terminal lacks.
For the shell itself, zsh ships default on macOS. Add oh-my-zsh or a minimal prompt like starship if you want git branch/status in your prompt without hand-rolling it.
7. A Brewfile so the next machine takes five minutes, not an afternoon
brew bundle dump --file=~/Brewfile
Captures every formula, cask, and Mac App Store app you have installed into one file. On the next machine:
brew bundle install --file=~/Brewfile
Keep this file in your dotfiles repo. This is the difference between reconstructing your setup from memory and running one command.
What to skip
Don’t install Docker Desktop by default if you don’t already know you need containers — it runs a background VM that eats RAM whether you’re using it or not. Same for a database GUI, a full IDE beyond your editor of choice, or any “might need it later” tool. Install it when a project actually requires it; a fresh machine with 20 unused apps is the thing you’re trying to avoid rebuilding from.