Mac language toolchain — one mise instead of nvm + pyenv + rbenv + goenv
Replace nvm, pyenv, rustup-light, goenv, and rbenv with a single tool. Per-project auto-switching, shell integration, and migration paths.
Year one you install nvm for Node. Year two you add pyenv. Year three you have rustup, goenv, and rbenv layered on top, and your .zshrc has grown by 30 lines. Each shell hook fights the others, and reproducing your setup on a new machine becomes a chore.
mise (formerly rtx) consolidates this into a single binary. asdf-compatible, written in Rust, fast and small. This guide centers on mise for Node · Python · Rust · Go · Ruby with a consistent workflow.
TL;DR
- One mise replaces every per-language version manager
- Pin per-project versions in
.mise.toml(or.tool-versions) - Auto-switch on
cd— no manualusecommand - One
mise installrehydrates every language/version on a new machine - Uninstall nvm/pyenv/rbenv — overlap is harmful
Prerequisites
- macOS 12+ with Homebrew (Mac initial setup)
- Optionally: existing nvm/pyenv state — see the migration section
1. Why mise
What it replaces
- Node.js (nvm, n, fnm, volta)
- Python (pyenv)
- Rust (partly — keep rustup for serious Rust work, see §10)
- Go (goenv)
- Ruby (rbenv, rvm)
- Java (jenv)
- Others — Elixir, Erlang, Lua, Bun, Deno, Terraform, kubectl, …
Strengths
- Fast — single Rust binary; supports shim-less PATH-only mode
- asdf-compatible —
.tool-versionsfiles work as-is - Auto-switch —
cdalone changes versions; nouseneeded - Global vs project —
mise use --globalvsmise use
Weaknesses
- Some rustup features missing — toolchain channels (stable/nightly/beta) and components (clippy, rustfmt, rust-analyzer) are smoother in rustup. mise is fine when you're pinning a single Rust version.
- Not a first-party language tool — occasional corner cases (e.g. Python's
python-buildoptions).
2. Install
brew install miseShell integration (append to .zshrc):
eval "$(mise activate zsh)"Open a new terminal and confirm with mise --version.
bash:
mise activate bash. fish:mise activate fish.
3. First use — install Node
# Browse available versions
mise ls-remote node
# Install globally
mise use --global node@22
# …or LTS
mise use --global node@ltsVerify:
node --version # v22.x
which node # ~/.local/share/mise/installs/node/22/bin/node4. Pin per-project versions
Inside your project folder:
cd ~/work/my-app
mise use node@20.18.0 # this project pins Node 20.18.0This creates .mise.toml:
[tools]
node = "20.18.0"Or the asdf-compatible form (.tool-versions):
node 20.18.0
python 3.12.7
.mise.tomltakes priority. Use.tool-versionsfor asdf compatibility or when migrating existing projects.
Auto-switching
cd alone makes mise pick up .mise.toml and switch versions. node --version flips immediately.
5. Multi-language project
cd ~/work/fullstack
mise use node@22
mise use python@3.12
mise use go@1.23.mise.toml:
[tools]
node = "22"
python = "3.12"
go = "1.23"mise install — installs every missing version in one shot.
6. Global vs local
mise use --global node@22 # ~/.config/mise/config.toml
mise use node@20 # .mise.toml in cwdPriority (highest first):
- Environment variable
MISE_NODE_VERSION .mise.toml/.tool-versionswalking up from cwd- Global
~/.config/mise/config.toml - The system PATH default
7. Daily commands
# All installed versions
mise ls
# Available remote versions of a tool
mise ls-remote python
# Install everything declared in lock files
mise install
# Upgrade a single tool
mise upgrade node
# Uninstall a version
mise uninstall node@18
# Inspect environment
mise env
# One-shot exec
mise exec -- node --version
mise x -- npm test8. .envrc / direnv integration
mise 0.30+ supports environment variables in .mise.toml:
[tools]
node = "22"
[env]
DATABASE_URL = "postgresql://localhost/myapp"
NODE_ENV = "development"A separate direnv setup is no longer needed — mise auto-exports env on cd.
9. Migrating from nvm/pyenv
nvm → mise
# Which Node are you on?
nvm current
# v20.18.0
# Install the same version under mise
mise use --global node@20.18.0
# Remove nvm
brew uninstall nvm
# or
rm -rf ~/.nvm
# Delete the nvm hook lines from .zshrcpyenv → mise
pyenv versions
# Confirm the versions in use
mise use --global python@3.12.7
# Remove pyenv
brew uninstall pyenv pyenv-virtualenv
rm -rf ~/.pyenv
# Clean up .zshrcasdf → mise
# Existing .tool-versions just works
brew uninstall asdf
brew install mise
# Swap the asdf line in .zshrc for the mise activate linemise is asdf-plugin-compatible for most plugins. Check via mise plugins ls-remote.
10. Rust — mise vs rustup
Rust's notion of toolchains (stable, nightly) and components (clippy, rustfmt, rust-analyzer) is powerful, and rustup handles it better. Use mise for Rust only when pinning a single version is enough.
Recommendations:
- Serious Rust dev: keep rustup. Manage other languages with mise.
- Occasional Rust: mise on one stable version is fine.
To keep rustup:
brew install rustup-init
rustup-init -y
# .zshrc auto-activates the rustup environmentVerification
mise --version— installation OKcd ~/work/proj-a && node --version→ project A's pinned versioncd ~/work/proj-b && node --version→ project B's version (auto-switched)mise install— install everything declared in.mise.toml- New machine:
brew install mise && mise install→ full rehydration
Troubleshooting
command not found: node
eval "$(mise activate zsh)"is missing from.zshrc- Open a new terminal, or
. ~/.zshrc
Auto-switch isn't happening
.mise.tomlor.tool-versionsshould be at the project rootmise currentshows what's active- Trust prompt — first time entering a new directory:
mise trustonce
pip install permission errors
mise's Python is user-local. Don't conflate with system Python. Prefer a venv:
python -m venv .venv
source .venv/bin/activate
pip install ...Node native module build fails
- mise's Node uses prebuilds —
npm rebuildor callnode-gypexplicitly - macOS needs Xcode CLT:
xcode-select --install
asdf community plugin missing
A few asdf community plugins (asdf-foo) aren't in mise's core list. Add manually: mise plugins install foo https://github.com/....
Non-deterministic builds
Pin exact versions in .mise.toml (node = "20.18.0", not "20"). Acts as a lock for team work.
References
- Mac initial setup — Homebrew prerequisite
- dotfiles management —
.mise.tomlbelongs in chezmoi - mise official
- asdf official — the comparison point
- rustup — for serious Rust
Changelog
- 2026-05-12 — Initial English translation (devAlice M3 i18n seed)