At some point I noticed I had nvm, pyenv, a Go install in /usr/local, a hand-downloaded terraform
binary, and direnv, each with its own shell hook. Every new machine meant an hour of setup.
mise handles all of that with one tool.
Install and activate
curl https://mise.run | sh
Then hook it into the shell. For fish:
# ~/.config/fish/config.fish
~/.local/bin/mise activate fish | source
For bash it’s eval "$(~/.local/bin/mise activate bash)".
Global defaults
mise use -g node@22 python@3.12 go@latest
This writes to ~/.config/mise/config.toml. Tools are downloaded on first use and live under
~/.local/share/mise, so nothing touches system packages.
Per-project versions
In a project directory:
mise use node@20 terraform@1.9
That creates a mise.toml you can commit:
[tools]
node = "20"
terraform = "1.9"
[env]
AWS_PROFILE = "dev"
cd into the directory and the right versions are on PATH, plus the environment variables, which
is what I used direnv for. Teammates run mise install once.
It also reads existing .nvmrc and .python-version files, which made migrating old repos easy.
Tasks
The part I didn’t expect to use is tasks. Small projects that had a Makefile only for convenience now have this instead:
[tasks.build]
run = "go build -o bin/app ./cmd/app"
[tasks.test]
run = "go test ./..."
mise run test works the same on every machine.
Verdict
One binary, one config format, and a new machine is ready after mise install. I removed nvm,
pyenv and direnv from my dotfiles and haven’t missed them.