Use mise (or asdf) for Tool Versioning
Install mise (or asdf) to manage Node, Python, Ruby, and more from one .tool-versions file — replacing nvm, pyenv, and rbenv with a single fast tool.
Before you start
- ▸A working terminal with bash, zsh, or fish
- ▸curl and git installed
- ▸Build tools (build-essential or Development Tools group) for compiled runtimes like Python and Ruby
- ▸Existing nvm/pyenv/rbenv removed or disabled to avoid PATH conflicts
Managing multiple versions of Node.js, Python, Ruby, or Go across projects is a common headache. Traditionally you'd reach for a separate tool per language — nvm for Node, pyenv for Python, rbenv for Ruby. mise (formerly rtx, a fast Rust rewrite inspired by asdf) handles all of them through a single interface with a shared .tool-versions file. asdf is the older, battle-tested original written in bash; both read the same config format, so switching is painless. This guide covers mise primarily, with asdf notes where they differ.
Why mise over nvm/pyenv/rbenv
Each per-language version manager adds shell hooks, PATH manipulations, and separate config files. mise replaces all of them with one binary, one config format, and hook injection that is measurably faster than asdf's shim approach. It activates versions lazily per-directory, so your shell startup stays quick.
- One
.tool-versionsfile per project (or a global fallback in~/.tool-versions) - Hundreds of plugins available through the asdf plugin ecosystem
- Drop-in compatible with existing asdf setups — reads the same config files
- Written in Rust; activation adds roughly 2–5 ms to shell startup vs 30–100 ms for asdf shims
Install mise
Recommended: the install script
The upstream script installs a static binary to ~/.local/bin/mise. Review it first if that is your policy.
curl https://mise.run | sh
Alternatively, install via your package manager:
# Debian/Ubuntu (via apt)
apt install -y gpg
curl -fsSL https://mise.jdx.dev/gpg-key.pub | gpg --dearmor | sudo tee /usr/share/keyrings/mise-archive-keyring.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/mise-archive-keyring.gpg arch=amd64] https://mise.jdx.dev/deb stable main" | sudo tee /etc/apt/sources.list.d/mise.list
sudo apt update && sudo apt install -y mise
# Fedora / RHEL / Rocky
dnf install -y dnf-plugins-core
dnf config-manager --add-repo https://mise.jdx.dev/rpm/mise.repo
dnf install -y mise
# Arch Linux (AUR)
yay -S mise-bin
# or with paru
paru -S mise-bin
Activate mise in your shell
Add the activation line to your shell's rc file. This sets up PATH and the directory-change hook that swaps versions automatically.
# bash — add to ~/.bashrc
echo 'eval "$(mise activate bash)"' >> ~/.bashrc
source ~/.bashrc
# zsh — add to ~/.zshrc
echo 'eval "$(mise activate zsh)"' >> ~/.zshrc
source ~/.zshrc
# fish — add to ~/.config/fish/config.fish
echo 'mise activate fish | source' >> ~/.config/fish/config.fish
source ~/.config/fish/config.fish
Install asdf (optional, if you prefer the original)
Skip this section if you chose mise. asdf requires git and a supported shell.
git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.1
# bash — add to ~/.bashrc
echo '. "$HOME/.asdf/asdf.sh"' >> ~/.bashrc
source ~/.bashrc
asdf uses shims — small wrapper scripts in ~/.asdf/shims/ — rather than PATH rewriting. After installing a tool version you must run asdf reshim <plugin> to generate them.
Add plugins and install tool versions
Finding and adding a plugin
mise bundles first-party support for the most common runtimes (node, python, ruby, go, java, rust via rustup). For others, use the asdf plugin registry.
# List bundled/known tools
mise registry
# Install a specific Node.js version globally
mise use --global node@22
# Install Python 3.12 globally
mise use --global [email protected]
# Install Ruby 3.3
mise use --global [email protected]
For a plugin not bundled natively, add the asdf plugin URL directly:
mise plugins add terraform https://github.com/asdf-community/asdf-hashicorp.git
mise use --global [email protected]
# asdf equivalent
asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git
asdf install nodejs 22.4.0
asdf global nodejs 22.4.0
The .tool-versions file
Drop a .tool-versions file in any project root. Both mise and asdf read it and activate the listed versions when you enter that directory.
cat .tool-versions
# node 22.4.0
# python 3.12.3
# ruby 3.3.1
Create or update it with mise use (no --global flag) from inside your project directory:
cd ~/projects/myapp
mise use [email protected]
mise use [email protected]
This writes the versions into the local .tool-versions. Commit it to version control so teammates get the same environment automatically — as long as they also have mise or asdf installed.
mise also supports its own mise.toml format (richer, supports environment variables and tasks), but .tool-versions is the portable choice when collaborating with asdf users.
Replacing nvm, pyenv, and rbenv
If you already use per-language managers, remove their shell hooks first to avoid conflicts.
Remove nvm
# Remove nvm lines from ~/.bashrc or ~/.zshrc, then:
rm -rf ~/.nvm
Remove pyenv
# Remove the pyenv init block from your rc file, then:
rm -rf ~/.pyenv
Remove rbenv
# Remove rbenv init from your rc file, then:
rm -rf ~/.rbenv
After removing each tool, reinstall the versions you need through mise:
mise use --global node@22 [email protected] [email protected]
Verify the setup
mise doctor
This checks activation, PATH, and plugin health. Fix any warnings it reports before continuing.
mise list
Output shows installed tools, their versions, and whether each is active (marked with a *). Example output (yours will differ):
# node 22.4.0 ~/.tool-versions
# python 3.12.3 ~/projects/myapp/.tool-versions
# ruby 3.3.1 ~/.tool-versions
node --version
python --version
ruby --version
Keeping tools updated
# See newer versions available for installed tools
mise outdated
# Upgrade a specific tool
mise upgrade node
# Upgrade all tools to latest within their declared constraints
mise upgrade
Troubleshooting
Command not found after installing a tool
If you used asdf, run asdf reshim <plugin> <version> to regenerate shims. With mise this is rare — if it happens, reload your shell with exec $SHELL and check mise doctor for PATH issues.
Wrong version activated
Run mise current to see which version is active and which file set it. A closer .tool-versions (or mise.toml) in a parent directory always wins. Check for a stale global override with mise list.
Build failures during install
Python and Ruby compile from source by default. Missing build dependencies are the most common cause. Install them first:
# Debian/Ubuntu
sudo apt install -y build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev
# Fedora/RHEL
sudo dnf groupinstall -y "Development Tools"
sudo dnf install -y openssl-devel zlib-devel bzip2-devel readline-devel sqlite-devel
Conflicts with system Python
mise-managed Python is only active in your shell session and directories where it is declared. System Python (used by the OS and system pip) is untouched. Never run mise use --global python on a system where OS tools depend on a specific Python — set it per-project instead.
Frequently asked questions
- Can I use mise and asdf on the same machine?
- Technically yes — they read the same .tool-versions files — but running both simultaneously causes PATH conflicts. Pick one and remove the other's shell activation lines.
- Does mise work with Wayland / GUI apps that need a specific runtime?
- mise only activates versions inside your shell session. GUI apps launched outside a terminal (e.g. from a desktop launcher) don't inherit mise's PATH, so they fall back to the system-installed version. Use a wrapper script or a systemd user service that sources mise for GUI use cases.
- Is .tool-versions enough, or should I use mise.toml?
- .tool-versions is portable across both mise and asdf, which is ideal for shared projects. mise.toml adds support for environment variables, tasks, and version constraints, and is the better choice for mise-only projects.
- Will mise slow down my shell startup?
- mise uses runtime PATH activation rather than shims, adding roughly 2–5 ms. That is noticeably faster than asdf's shim approach, which can add 30–100 ms on slower hardware.
- How do I set a version for just one command without changing .tool-versions?
- Use mise exec: for example, `mise exec [email protected] -- python myscript.py` runs the script under Python 3.11 without affecting the directory's declared version.
Related guides
Bash Arrays and Associative Arrays
Master bash indexed and associative arrays: declaration, element access, looping, mapfile, namerefs, and practical patterns for real scripting work.
Bash Functions and Variable Scoping
Master Bash function scoping with local variables, source-based libraries, correct use of return codes, and array passing techniques including namerefs.
Bash Loops: for, while and until
Learn all three Bash loop types — for, while, and until — with practical, copy-paste examples covering file iteration, counting, polling, and safe line reading.
Bash Scripting for Beginners
Learn Bash scripting from scratch: shebang lines, variables, conditionals, loops, and arguments, plus a real backup script to tie it all together.