macOS Development Environment Setup
Step-by-step guide to setting up a macOS development environment with Homebrew, shell configuration, language runtimes, Git dual-identity setup, containers, and automated deployment.
The most common pitfalls when configuring a macOS environment for the first time or after a reinstall revolve around toolchain versions, installation paths, and permission control. This article organizes a general workflow in the order of “System Preparation → Package Management → Terminal & Shell → Language Runtimes → Version Control → Containers & Common Tools” to help you get development work up and running quickly.
One-Click Automation: The automation script corresponding to this article is available at bitnpc/mac-dev-setup. Run
make allto complete the entire configuration. Each section below has a corresponding Makefile target and Shell script, suitable for side-by-side reading.
- System Update: It is recommended to upgrade to the latest stable version of macOS (
Settings → General → Software Update), and apply security updates at the same time. - Disk & Permissions: In
System Settings → Privacy & Security, grant Full Disk Access to the terminal and development tools to avoid frequent permission prompts during installation. - Command Line Tools Check: Install Command Line Tools via
xcode-select --install; if already installed, usexcode-select --print-pathto verify the path points to the expected location.
Essential for iOS/macOS App development. The latest version can be downloaded from the App Store, and historical versions are available from the Developer Center.
# Current default Xcode path used by the systemxcode-select --print-path
# Switch between multiple versionssudo xcode-select -switch /Applications/Xcode.app/Contents/Developer
# Install Command Line Tools onlyxcode-select --installAutomation equivalent: make bootstrap → scripts/bootstrap.sh
Homebrew is the de facto standard package manager on macOS, responsible for installing, upgrading, and uninstalling CLI tools and GUI applications. It is the core of automated environment maintenance.
# Install (works for both Intel and Apple Silicon)/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Uninstall/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/uninstall.sh)"After installation, follow the script prompts to inject brew shellenv into your current Shell, for example:
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofileeval "$(/opt/homebrew/bin/brew shellenv)"It is recommended to use a Brewfile to manage all dependencies centrally. The Brewfile serves as the single package manifest for the entire environment — CLI tools, GUI applications, and fonts are all declared within it.
# Export current dependencies as a Brewfilebrew bundle dump --describe
# Install dependencies from Brewfilebrew bundle installAutomation equivalent: make brew → scripts/install_brew_bundle.sh
- Mirror Switching: For users in China, Tsinghua / USTC mirrors are recommended. Set
HOMEBREW_BREW_GIT_REMOTEandHOMEBREW_CORE_GIT_REMOTEbefore installation, or use community scripts for batch replacement. - Permission Issues: Apple Silicon installs Homebrew to
/opt/homebrewby default; on Intel machines, usesudo chown -R $(whoami) /usr/local/*to fix legacy permission issues. - Environment Variable Residue: After reinstalling or migrating, clean up old
brew shellenventries in~/.bash_profileand~/.zprofileto prevent pointing to invalid paths.
Reference links:
macOS’s built-in Terminal has limited functionality. It is recommended to pair iTerm2, Oh My Zsh, Starship, and useful plugins to create an efficient terminal environment.
Automation equivalent: make shell → scripts/setup_shell.sh
brew install --cask iterm2# Install Oh My Zsh (skip if already installed)sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
# Set zsh as the default Shellchsh -s /bin/zsh
# Install common plugins (autocomplete / syntax highlighting)brew install zsh-autosuggestions zsh-syntax-highlighting
# Install Starship cross-shell promptbrew install starship
# Append to ~/.zshrccat <<'EOF' >> ~/.zshrcsource /opt/homebrew/share/zsh-autosuggestions/zsh-autosuggestions.zshsource /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsheval "$(starship init zsh)"EOFThe system Python that ships with macOS is relatively old. It is recommended to use pyenv to manage multiple versions.
Automation equivalent: setup_languages.sh (as part of make languages)
# Install pyenv and virtual environment pluginbrew install pyenv pyenv-virtualenv pipx
# Initialize (note that pyenv requires hooks in both .zprofile and .zshrc)echo 'eval "$(pyenv init --path)"' >> ~/.zprofileecho 'eval "$(pyenv init -)"' >> ~/.zshrcsource ~/.zprofile
# Install a specific version (default 3.11.6)pyenv install 3.11.6pyenv global 3.11.6
# (Optional) Create a virtual environmentpyenv virtualenv 3.11.6 project-envpyenv activate project-envCommon helper tools:
pipx: Install CLI tools in isolation (example:pipx install httpie);poetry/pipenv: Manage project dependencies and virtual environments;uv: Quickly create virtual environments and install dependencies, a newer alternative compatible with pip/virtualenv;- Set
export PIP_REQUIRE_VIRTUALENV=truein~/.zshrcto avoid accidentally installing packages to the global environment.
It is recommended to use the lightweight version manager rbenv to avoid injecting too many Shell hooks. If you prefer a unified manager across multiple languages, consider asdf with a .tool-versions file.
Automation equivalent: setup_languages.sh (as part of make languages)
# Install rbenv and ruby-build (ruby-build provides the install subcommand)brew install rbenv ruby-build
# Initializeecho 'eval "$(rbenv init - zsh)"' >> ~/.zshrcsource ~/.zshrc
# Install a specific version (default 3.2.2, note that OpenSSL path must be specified for Apple Silicon)RUBY_VERSION=3.2.2rbenv install $RUBY_VERSION --with-openssl-dir=$(brew --prefix openssl@3)rbenv global $RUBY_VERSION
# Install common gemsgem install bundler cocoapodsIt is recommended to use Volta, which provides a great experience on Apple Silicon and can automatically pin versions in a project’s package.json.
Automation equivalent: setup_languages.sh (as part of make languages)
# Install Voltabrew install volta
# Volta automatically registers itself in PATH (needs initialization in ~/.zshrc or ~/.zprofile)# After reloading the shell, install node/npmvolta install node@20volta install pnpmVolta pins the selected version globally, while also allowing per-project version overrides declared via the volta field in package.json. It automatically switches runtime versions when you change project directories, with no need for manual nvm use.
Common toolchain:
- Package managers:
npm,yarn,pnpm, withcorepack enablefor unified management; - Formatting & Linting: Add
eslint,prettier,typescriptto projectdevDependenciesand invoke vianpx; - Monorepo: Optionally use
turbo,nx, orlage, pinning versions first withvolta pin.
Automation equivalent: setup_languages.sh (as part of make languages)
# Gobrew install gomkdir -p $HOME/go/bin# Add to ~/.zshrc:# export GOPATH=$HOME/go# export GOBIN=$HOME/go/bin# export PATH=$GOBIN:$PATH
# Rustbrew install rustup-initrustup-init -y --no-modify-pathrustup component add rustfmt clippyEnterprise development typically requires maintaining both a corporate identity (GitLab / self-hosted Git services) and a personal identity (GitHub), each with different usernames, emails, and SSH keys. This section provides an automatable dual identity solution that leverages Git’s init.templateDir mechanism to automatically switch identities on a per-repository basis.
Automation equivalent: make git → scripts/setup_git.sh
brew install git gh glab
# Global default identity — corporate identitygit config --global user.name "Your Name"git config --global user.email "your@company.com"git config --global init.defaultBranch maingit config --global core.autocrlf inputgit config --global pull.rebase falseCore concept: Use Git’s init.templateDir to inject a post-checkout hook on every git clone. After the first checkout, the hook automatically detects whether the remote repository is github.com and, if so, adds the GitHub identity as a local configuration.
# 1. Create a separate GitHub identity configcat > ~/.gitconfig-github <<EOF[user] name = your-github-username email = your-github@email.comEOF
# 2. Set init.templateDirgit config --global init.templateDir ~/.git-template/hooks/
# 3. Create the post-checkout hookmkdir -p ~/.git-template/hookscat > ~/.git-template/hooks/post-checkout <<'HOOK'#!/bin/zsh# Only execute on the initial clone checkout (SHA1 is all zeros)if [[ "$1" = "0000000000000000000000000000000000000000" ]]; then remote=$(git remote get-url origin 2>/dev/null) if echo "$remote" | grep -q "github.com"; then git config include.path ~/.gitconfig-github echo "✅ GitHub identity applied for $(basename $(git rev-parse --show-toplevel))" fifiHOOKchmod +x ~/.git-template/hooks/post-checkoutAfter this, all repositories cloned via git clone require no additional steps — GitHub repositories automatically use the separate identity, while corporate repositories use the global default identity.
# Generate default keyssh-keygen -t ed25519 -C "your@company.com"
# Generate GitHub-specific key (does not overwrite the default key)ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_github -C "your-github@email.com"
# Add to ssh-agent (Apple Silicon supports --apple-use-keychain)ssh-add --apple-use-keychain ~/.ssh/id_ed25519ssh-add --apple-use-keychain ~/.ssh/id_ed25519_githubcat > ~/.ssh/config <<EOFHost * AddKeysToAgent yes UseKeychain yes IdentityFile ~/.ssh/id_ed25519
Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_githubEOFRecommended companion configuration:
- Log in to GitHub CLI with
gh auth login; - Enable
AddKeysToAgent yescombined withssh-add --apple-use-keychainto avoid repeatedly entering passwords.
Automation equivalent: make containers → scripts/setup_containers.sh
# Colima (lightweight Docker runtime, recommended for Apple Silicon)brew install dockercolima start --arch aarch64 --runtime docker --vm-type=vz --mount-type=virtiofs --memory 4 --cpu 4
# Podman Desktop (includes CLI, no Docker Desktop dependency)brew install podman-desktoppodman machine init --nowParameter explanation:
--vm-type=vz: Uses Apple Virtualization.framework for better performance;--mount-type=virtiofs: File sharing performance is superior to the default 9p;--memory 4 --cpu 4: Adjust according to physical resources.
Dev Containers: VS Code + devcontainer.json for cross-platform consistent containerized development environments. Virtual Machines: UTM for running Linux / Windows test environments.
The following tools are declared in the project’s Brewfile, with CLI tools and GUI applications installed by make brew and make apps respectively:
- Terminal: iTerm2
- Editor: Visual Studio Code, JetBrains Toolbox
- AI Coding: Claude Code (Anthropic CLI coding assistant), cc-switch-cli (multi-AI agent configuration switching)
- Browser: Google Chrome
- Productivity: Raycast, Hammerspoon
- Database Clients: TablePlus, Postico, MongoDB Compass
- Network Debugging: Proxyman, Wireshark
- Container Management: Podman Desktop
- Virtualization: UTM
- Git GUI: Fork
- Fonts: JetBrainsMono Nerd Font, MesloLGS NF
- Mac App Store: Managed via
masCLI
All of the above steps can be completed with a single command using bitnpc/mac-dev-setup, which supports three deployment methods:
make all # Fully automatic: bootstrap → brew → shell → languages → git → containers → apps → validatemake bootstrap # Xcode CLT + Homebrewmake brew # Brewfile installation (CLI tools)make shell # Oh My Zsh + plugins + Starshipmake languages # Python/Ruby/Node/Go/Rust runtimesmake git # Git dual identity + SSHmake containers # Colima + Podmanmake apps # GUI applications + fontsmake validate # 18-item self-checkAll scripts (scripts/*.sh) are designed to be idempotent (safe to re-run).
make chezmoiThe chezmoi/ directory provides complete dotfile templates (.zshrc, .zprofile, .gitconfig, .ssh/config, etc.), using Go template syntax for conditional rendering and variable injection. Some templates are auto-generated by setup scripts (such as language version information, Go PATH configuration).
make ansibleansible/mac_dev.yml implements the same workflow as a declarative Ansible playbook, suitable for consistent deployment across multiple Macs on a team.
# Check item by item (recommended)make validate
# Or check manuallyxcode-select --print-pathbrew doctorgit --versionpython3 --versionnode --versiondocker infoscripts/validate.sh checks whether the following 18 items are ready: Xcode CLT, Homebrew, Git, Python, pyenv, Ruby, rbenv, Node.js, Volta, Go, Rust (rustc), Cargo, Docker, Colima, Podman, Starship, chezmoi.
- Command not found: Ensure
PATHincludes/opt/homebrew/binand/opt/homebrew/sbin. Addeval "$(/opt/homebrew/bin/brew shellenv)"at the top of~/.zprofile. - Rosetta Support: Run
softwareupdate --install-rosettato install Rosetta 2, and usearch -x86_64to run legacy Intel-only software. - Permissions & Security Policies: If you encounter “cannot be opened because the developer cannot be verified”, right-click and select “Open” in Finder, or run
xattr -d com.apple.quarantine <file>. - Network Restrictions: Prepare a VPN / proxy, or configure mirrors for Homebrew, npm, pip, etc., to avoid long timeouts during installation.
- Update the system and install Command Line Tools first to ensure a consistent base toolchain;
- Use Homebrew to manage CLI / GUI software, combined with Brewfile and dotfiles for reproducible deployment;
- Use iTerm2, Oh My Zsh / Starship, and plugins to boost terminal efficiency;
- Manage multi-language runtimes with pyenv, rbenv, volta, and similar tools;
- Use the
init.templateDir+post-checkouthook approach to manage Git dual identities without manual switching; - Automate the entire configuration into
Makefilescripts — runmake allon a new machine to get started quickly.