mise: The Dev Tool Manager That Replaced asdf, nvm, and pyenv
mise: The Dev Tool Manager That Replaced asdf, nvm, and pyenv
mise (pronounced "meez") is a polyglot development tool manager written in Rust. It manages runtime versions (Node, Python, Ruby, Go, Java, and hundreds more), sets environment variables per project, and runs project tasks -- all from a single tool with a single config file.
If you are currently juggling nvm for Node, pyenv for Python, and rbenv for Ruby, mise replaces all three. If you are using asdf, mise is its faster, more capable successor that reads the same .tool-versions files.
Installation
# macOS
brew install mise
# Linux (recommended)
curl https://mise.run | sh
# Or via cargo
cargo install mise
After installation, activate mise in your shell:
# Add to ~/.bashrc or ~/.zshrc
eval "$(mise activate bash)" # for bash
eval "$(mise activate zsh)" # for zsh
eval "$(mise activate fish)" # for fish
Restart your shell or source the config file.
Basic Usage: Managing Runtimes
Installing Tools
# Install a specific Node version
mise use [email protected]
# Install the latest Python 3.12.x
mise use [email protected]
# Install the latest Go
mise use go@latest
# Install globally (applies to all projects)
mise use --global node@22
mise use both installs the tool and sets it as active for the current directory. It updates your .mise.toml config file automatically.
Listing and Switching
# See what's installed
mise ls
# See available versions for a tool
mise ls-remote node | tail -20
# Switch versions
mise use node@20
# See which version is active and why
mise where node
mise which node
How It Works
When you run node, mise intercepts the call through a shim (or shell hook) and routes it to the correct version based on your project's config file. The lookup order is:
.mise.tomlin current directory (or parents).tool-versionsin current directory (or parents)~/.config/mise/config.toml(global default)
This means each project can pin its own versions without affecting other projects.
Configuration: .mise.toml
The .mise.toml file is mise's native config format. It is more expressive than asdf's .tool-versions.
[tools]
node = "22"
python = "3.12"
bun = "latest"
terraform = "1.7"
[env]
DATABASE_URL = "postgres://localhost:5432/myapp_dev"
NODE_ENV = "development"
AWS_PROFILE = "myapp-dev"
# Load environment from a .env file
[env]
_.file = ".env"
[tasks.dev]
run = "bun run dev"
description = "Start development server"
[tasks.test]
run = "bun test"
description = "Run tests"
[tasks.lint]
run = "biome check ."
description = "Run linter"
[tasks.db-migrate]
run = "bun run drizzle-kit push"
description = "Run database migrations"
depends = ["db-check"]
[tasks.db-check]
run = "pg_isready -h localhost"
description = "Check database is running"
Environment Variables
mise manages environment variables per project, replacing direnv for many use cases:
[env]
# Static values
API_KEY = "dev-key-123"
PORT = "3000"
# Reference other env vars
HOME_BIN = "{{env.HOME}}/bin"
# Load from files
_.file = [".env", ".env.local"]
# Add to PATH
_.path = ["./node_modules/.bin", "./scripts"]
When you cd into a project directory, mise automatically sets these variables. When you leave, they are unset. No more leaking development credentials into unrelated projects.
Security note: mise will prompt for confirmation the first time it loads a .mise.toml that sets environment variables, preventing malicious repos from silently exporting variables.
Task Runner
mise includes a built-in task runner that replaces Makefiles, npm scripts, and Just for many workflows.
[tasks.build]
run = """
bun run build
cp -r dist/ deploy/
"""
description = "Build and prepare for deployment"
[tasks.deploy]
run = "rsync -avz deploy/ server:/var/www/app/"
depends = ["build", "test"]
description = "Deploy to production"
[tasks.clean]
run = "rm -rf dist/ deploy/ node_modules/.cache"
description = "Clean build artifacts"
Run tasks with:
mise run dev
mise run test
mise run deploy
# List available tasks
mise tasks
Tasks can depend on other tasks, accept arguments, and run in parallel:
[tasks.ci]
depends = ["lint", "test", "build"]
description = "Run full CI pipeline"
[tasks.test-unit]
run = "bun test --filter unit"
[tasks.test-integration]
run = "bun test --filter integration"
[tasks.test]
depends = ["test-unit", "test-integration"]
wait_for = ["db-check"]
Tasks vs npm Scripts vs Make vs Just
| Feature | mise tasks | npm scripts | Make | Just |
|---|---|---|---|---|
| Dependencies between tasks | Yes | No | Yes | No |
| Parallel execution | Yes | With concurrently | Yes | No |
| Language-agnostic | Yes | Node only | Yes | Yes |
| Argument passing | Yes | Awkward | Yes | Yes |
| Environment per task | Yes | No | No | Yes |
| Built into your version manager | Yes | No | No | No |
The advantage of mise tasks is consolidation. Your version management, environment variables, and task runner are all in one .mise.toml file.
Migrating from Other Tools
From asdf
mise reads .tool-versions files natively. Migration is often zero-effort:
# Your existing .tool-versions works as-is
# nodejs 22.5.0
# python 3.12.4
# Optionally convert to .mise.toml
mise settings set experimental true
mise config migrate
Most asdf plugins work with mise. The asdf: prefix lets you explicitly use asdf plugin backends:
[tools]
"asdf:hashicorp/terraform" = "1.7.0"
From nvm
# Remove nvm from your shell config
# Replace .nvmrc with mise config
# If you have .nvmrc with "22"
echo 'node = "22"' > .mise.toml
# mise also reads .nvmrc files if you prefer not to migrate
From pyenv
# Remove pyenv from your shell config
# Replace .python-version with mise config
# mise reads .python-version files
# Or set in .mise.toml:
# [tools]
# python = "3.12"
From rbenv
Same pattern: mise reads .ruby-version files. Remove rbenv from your shell config, install mise, and your existing version files work without changes.
Team Workflows
Pinning Versions for Consistency
Commit your .mise.toml to version control. When a team member clones the repo:
# Automatically installs all required tools
mise install
# Everything matches the committed config
mise ls
This eliminates "works on my machine" problems caused by version mismatches.
Trust Model
mise requires explicit trust before loading project configs that set environment variables or run hooks:
# Trust a project's config
mise trust
# Trust a specific file
mise trust .mise.toml
# Revoke trust
mise trust --untrust
This prevents a cloned repo from silently running commands or setting variables. You must explicitly opt in for each project.
Performance
mise is written in Rust and noticeably faster than asdf (written in shell scripts):
| Operation | mise | asdf |
|---|---|---|
node --version (shim overhead) |
~5ms | ~200ms |
install node@22 |
Same (downloads same binary) | Same |
| Shell startup (activation) | ~10ms | ~50-100ms |
ls (list installed) |
Instant | ~500ms |
The shim overhead matters most. If asdf adds 200ms to every command invocation, that compounds across a development session. mise's overhead is imperceptible.
Advanced Configuration
Hooks
Run commands automatically when entering or leaving a project directory:
[hooks]
enter = "echo 'Entering project: {{config_root}}'"
cd = "mise install --quiet"
Settings
Global settings in ~/.config/mise/config.toml:
[settings]
# Always install missing tools automatically
auto_install = true
# Use symlinks instead of shims (faster, but less compatible)
shims_dir = "~/.local/share/mise/shims"
# Number of parallel jobs for installs
jobs = 4
# Quiet output
quiet = false
Integration with CI
# GitHub Actions
- name: Install mise
uses: jdx/mise-action@v2
- name: Install tools
run: mise install
- name: Run tests
run: mise run test
This ensures your CI environment matches your development environment exactly.
When Not to Use mise
mise is not the right tool if you only use a single runtime and have no plans to add more. If you only write Python and already have a working pyenv setup, the migration offers minimal benefit.
It is also not a container-based dev environment tool. If you want fully reproducible environments with system-level dependencies, look at Dev Containers or Nix instead. mise manages runtimes and tools, not operating system packages.
For everyone else -- anyone managing two or more runtimes, wanting per-project environment variables, or tired of maintaining separate version managers -- mise is the current best-in-class tool.