Managing runtime versions is one of those things you don’t think about much — until you switch projects and something breaks. For a long time, I used rbenv and nvm to handle the versions of Ruby and Node.js on this project. Each worked fine on its own, but the friction was always there. Two separate tools, two config files, and two things to remember when getting back to the project. Something was feeling off.

And then I found mise, and I had one of those aha moments where everything falls into place.

The problem with multiple version managers

If you work across different languages, you probably have a setup that looks something like this:

  • ~/.rbenv + rbenv shell integration + .ruby-version per project
  • ~/.nvm + nvm shell integration + .nvmrc per project
  • Maybe pyenv for Python, or goenv for Go…

Each version manager has its own installation method, its own shell hook, its own version file format, and its own quirks. When you clone a new repo, you need to check which version files are present and install the right runtimes manually. It works, but it’s friction. And that’s one of the problems that mise tries to solve.

What is mise

mise (pronounced “meez”) is a polyglot version manager. It replaces rbenv, nvm, pyenv, goenv, and similar tools with a single binary. It reads a .mise.toml file in your project root and automatically activates the correct versions when you cd into the directory.

Key features:

  • One config file: .mise.toml lists all your project’s runtimes and versions
  • Automatic switching: mise activates the right versions as you navigate between projects
  • Wide language support: Ruby, Node.js, Python, Go, Rust, and many more
  • No shell plugins needed: uses a shell hook (eval "$(mise activate bash/zsh)") instead of per-tool plugins

Before and after

Here’s what my setup looked like before:

# Before: .ruby-version
ruby 3.3.4

# Before: .nvmrc
22.2.0

Two files, two tools, two shell integrations.

After switching to mise, everything lives in one file:

# After: .mise.toml
[tools]
ruby = "3.3.4"
node = "22.2.0"

This is the actual .mise.toml file from this blog’s repository.

Migration

Migrating was straightforward. Here’s what I did:

1. Install mise

curl https://mise.run | sh

Then add the shell hook to your .zshrc (or .bashrc) :

eval "$(~/.local/bin/mise activate zsh)"

2. Configure your tools

Create a .mise.toml file in your project root with the runtimes you need:

# mise.toml
[tools]
ruby = "3.3.4"
node = "22.2.0"

Then run mise install to download and install the versions specified in the file. mise reads the config automatically when you cd into the project directory.

3. Remove the old managers

Once everything works with mise, you can remove rbenv and nvm:

# Remove rbenv
rm -rf ~/.rbenv
# Remove rbenv from .zshrc / .bashrc

# Remove nvm
rm -rf ~/.nvm
# Remove nvm from .zshrc / .bashrc

Don’t forget to also delete the old .ruby-version and .nvmrc files from your projects — mise uses .mise.toml instead.

Conclusion

Switching from rbenv and nvm to mise took minutes, and removed a layer of friction I’d been tolerating for years. If your version manager setup feels heavier than it should, give mise a try.

Thanks for reading! If you found this post useful, consider buying me a coffee to support the blog. For questions or comments, feel free to reach out on X!

Until next time!