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.
Even if you are not developing apps, it is recommended to install Xcode Command Line Tools — they provide clang, git, and other basic tools that many third-party dependencies rely on by default.
# 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)"EOFYou can use chezmoi to manage dotfiles (make chezmoi). The chezmoi/ directory provides complete ~/.zshrc and ~/.zprofile templates with support for conditional rendering and variable injection.
The 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 clippyVersion Overrides: The default versions for the language runtimes above are defined at the top of scripts/setup_languages.sh (e.g., PYTHON_VERSION=3.11.6, RUBY_VERSION=3.2.2, NODE_VERSION=20). They can be temporarily modified via environment variables before running: NODE_VERSION=22 make languages.
Enterprise 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.
For existing local repositories, manually append with git config include.path ~/.gitconfig-github.
# 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
Mac App Store applications require running mas signin first to log in.
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.