TL;DR:

  • mise is a polyglot runtime version manager — install it once, and it manages Node.js, Python, Ruby, Go, Rust, Java, and 50+ other runtimes using a single .tool-versions file
  • It’s significantly faster than nvm or pyenv because it’s written in Rust and doesn’t require shell function hooks for every command
  • Compatible with existing .nvmrc, .python-version, and .tool-versions files — migration is mostly dropping mise into your workflow
  • Install: curl https://mise.run | sh

If you’ve spent any time setting up a development environment from scratch, you know the version manager tax. There’s nvm for Node. pyenv for Python. rbenv (or rvm) for Ruby. goenv for Go. jenv for Java. Each has its own shell integration, its own config file format, its own quirks, and they all slow down your shell startup in their own special way.

mise — pronounced “meez,” from the French culinary term mise en place — solves this by being a single tool that handles all of them. It was originally called rtx (a play on asdf, its predecessor), renamed in 2023, and has been gaining adoption steadily as teams standardise on it. By 2026 it’s the default version manager recommendation for polyglot shops, particularly those where Python and Node.js coexist in the same repositories.

Installation

# macOS
brew install mise

# Linux / WSL
curl https://mise.run | sh

# Or via cargo if you prefer
cargo install mise

Then add the shell integration. This is a one-time setup:

# Add to ~/.bashrc or ~/.zshrc
eval "$(mise activate bash)"
# or
eval "$(mise activate zsh)"
# or for fish
mise activate fish | source

The shell activation is lighter than nvm or pyenv’s hooks. mise uses a PATH manipulation approach that’s faster than the shell function wrapping those tools use.

Installing Runtimes

# Install specific versions
mise install node@22
mise install python@3.12
mise install ruby@3.3

# Install the latest stable
mise install node@latest
mise install python@latest

# List available versions
mise ls-remote node
mise ls-remote python

# See what's installed
mise ls

For each runtime, mise downloads pre-built binaries where available. For runtimes without pre-built binaries for your platform, it falls back to compilation — same as asdf or pyenv. Node.js, Python, Ruby, and Go all have pre-built binaries and install quickly.

Setting Versions Per Project

mise uses .tool-versions files — the same format as asdf — to pin versions per directory:

# In your project directory
mise use node@22
mise use python@3.12

This creates or updates .tool-versions:

node 22.2.0
python 3.12.3

When you cd into the directory, mise automatically activates those versions. When you cd out, the global versions resume. No manual nvm use or pyenv local required.

Compatibility with existing files: mise reads .nvmrc and .python-version files automatically. If you have a project with an .nvmrc containing 22, mise will activate node 22 when you enter that directory, even without a .tool-versions file. This makes adopting mise in an existing codebase with mixed version manager conventions painless.

Global Defaults

# Set global defaults
mise use --global node@22
mise use --global python@3.12

# This writes to ~/.config/mise/config.toml

The global config is in TOML rather than a dotfile, which is cleaner than nvm’s ~/.nvmrc or pyenv’s ~/.python-version.

The .tool-versions File in Team Workflows

The main reason to standardise on mise across a team is the .tool-versions file. Commit it to your repository, and anyone who has mise installed gets the same runtime versions automatically when they enter the project directory.

# .tool-versions
node 22.2.0
python 3.12.3
terraform 1.8.0

Compared to instructions like “make sure you have Node 22 installed, use nvm or whatever you prefer,” this is an objectively better developer experience. The versions are declarative, version-controlled, and enforced automatically.

A useful pattern for onboarding: mise install (without arguments) reads the current directory’s .tool-versions and installs any missing runtimes. Add this to your project’s setup script:

#!/bin/bash
# setup.sh
command -v mise >/dev/null 2>&1 || { echo "Install mise: curl https://mise.run | sh"; exit 1; }
mise install
npm install
pip install -r requirements.txt

mise vs asdf

mise is a drop-in replacement for asdf and uses the same plugin system and .tool-versions format. The two main differences:

  1. Speed: mise is written in Rust. asdf is written in bash. The practical difference is noticeable — mise commands execute in milliseconds where asdf can take 100–300ms. In shell startup and frequent cd-ing, this adds up.

  2. Built-in runtimes: asdf requires a plugin for every runtime. mise has Node.js, Python, Ruby, Go, and several others built in, with the asdf plugin system available as a fallback for anything else. If you’re using exotic runtimes, the asdf plugin ecosystem (1000+ plugins) is accessible through mise’s --plugin asdf mechanism.

  3. Active development: mise is more actively maintained than asdf as of 2026, with more frequent releases and faster bug resolution.

If you’re currently on asdf, migration is: install mise, add the shell activation, and your existing .tool-versions files work unchanged.

mise vs nvm (for Node-only shops)

If your team uses only Node.js, nvm is still a reasonable choice — it’s simple, widely documented, and the ecosystem has years of nvm-specific instructions. But mise’s advantages become compelling at scale:

  • No shell slowdown: nvm’s shell function approach adds 50–200ms to shell startup. mise does not.
  • CI integration: mise is easier to cache in CI because it uses a flat directory structure. nvm’s installation model is harder to cache correctly.
  • Future flexibility: if you ever add Python, Go, or any other runtime to your stack, you don’t need another version manager.

The --no-use flag in nvm startup scripts (export NVM_LAZY_LOAD=true or similar workarounds) exists specifically because of the startup time problem. mise doesn’t have this problem.

Environment Variables and Secrets

mise extends beyond runtime versions to environment variable management — effectively replacing .env files for many workflows:

# mise.toml (alternative to .tool-versions with extended config)
[env]
DATABASE_URL = "postgres://localhost/myapp_dev"
LOG_LEVEL = "debug"

[tools]
node = "22"
python = "3.12"

Variables defined in [env] are set automatically when you enter the directory and unset when you leave. This is similar to direnv, and mise can actually load .envrc files if you prefer keeping direnv conventions.

For secrets, mise integrates with 1Password, Bitwarden, and other secret managers through its mise secrets extension. The practical pattern:

[env]
API_KEY = "op://Personal/myapp/api-key"  # 1Password reference

This avoids committing secrets to .env files or having to remember to run source .env before working.

CI/CD Integration

mise has a GitHub Actions action (jdx/mise-action) that reads your .tool-versions file and installs the specified runtimes:

# .github/workflows/test.yml
steps:
  - uses: actions/checkout@v4
  - uses: jdx/mise-action@v2
  - run: npm test
  - run: python -m pytest

The action caches downloads between runs using the .tool-versions content as a cache key. For a Node + Python project, the tool installation step typically takes 5–15 seconds on a warm cache rather than 1–3 minutes for a cold install.

For other CI systems, the mise install --non-interactive flag handles unattended installation, and the MISE_DATA_DIR environment variable controls where binaries are cached for layer caching.

Common Gotchas

Shell integration is required: mise needs the eval "$(mise activate ...)" line in your shell config. Without it, version switching won’t work automatically. If you’re seeing the wrong version after installation, this is almost certainly the cause.

Restart your shell or source your config after adding the activation line. New terminal windows work; the current one doesn’t until re-sourced.

Global node/python are separate from system runtimes: mise installs runtimes in ~/.local/share/mise/installs/. The system Python or Node.js (installed via apt, brew, or the installer) is separate. Running node --version in a directory with no .tool-versions will use your mise global version, not the system version.

Docker images: For containerised development, the standard approach is to install mise in the base image and use COPY .tool-versions . followed by RUN mise install. The mise documentation has Docker examples for common base images.

The Practical Argument for Standardising

The most persuasive argument for mise isn’t any individual feature — it’s the reduction in environment-related debugging. “Works on my machine” problems are frequently version-related. A .tool-versions file committed to the repository and enforced automatically eliminates an entire category of these problems.

For engineering teams, that’s worth the ten minutes of migration effort from nvm or pyenv. For solo developers, it’s worth the convenience of not thinking about which version manager to reach for when you start a new project.

The adoption path is easy: install mise, add the shell activation, and your existing version manager files continue to work while you learn the mise-native conventions at your own pace.