devAlice
← Mac

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

  1. One mise replaces every per-language version manager
  2. Pin per-project versions in .mise.toml (or .tool-versions)
  3. Auto-switch on cd — no manual use command
  4. One mise install rehydrates every language/version on a new machine
  5. 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-versions files work as-is
  • Auto-switchcd alone changes versions; no use needed
  • Global vs projectmise use --global vs mise 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-build options).

2. Install

brew install mise

Shell 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@lts

Verify:

node --version    # v22.x
which node        # ~/.local/share/mise/installs/node/22/bin/node

4. Pin per-project versions

Inside your project folder:

cd ~/work/my-app
mise use node@20.18.0    # this project pins Node 20.18.0

This creates .mise.toml:

[tools]
node = "20.18.0"

Or the asdf-compatible form (.tool-versions):

node 20.18.0
python 3.12.7

.mise.toml takes priority. Use .tool-versions for 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 cwd

Priority (highest first):

  1. Environment variable MISE_NODE_VERSION
  2. .mise.toml / .tool-versions walking up from cwd
  3. Global ~/.config/mise/config.toml
  4. 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 test

8. .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 .zshrc

pyenv → 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 .zshrc

asdf → mise

# Existing .tool-versions just works
brew uninstall asdf
brew install mise
 
# Swap the asdf line in .zshrc for the mise activate line

mise 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 environment

Verification

  1. mise --version — installation OK
  2. cd ~/work/proj-a && node --version → project A's pinned version
  3. cd ~/work/proj-b && node --version → project B's version (auto-switched)
  4. mise install — install everything declared in .mise.toml
  5. 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.toml or .tool-versions should be at the project root
  • mise current shows what's active
  • Trust prompt — first time entering a new directory: mise trust once

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 rebuild or call node-gyp explicitly
  • 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

Changelog

  • 2026-05-12 — Initial English translation (devAlice M3 i18n seed)

Comments