Mise: Modern Dev Tool Management
Mise is a polyglot version manager that replaces nvm, pyenv, rbenv, and most Homebrew CLI tools with a single, fast, declarative system.
Related skills:
- •just-pro - Build system setup (includes mise integration patterns)
- •cli-tools - Power CLI tools (many installable via mise)
Why Mise?
| Problem | Old Way | Mise Way |
|---|---|---|
| Node versions | nvm, fnm, volta | mise use node@22 |
| Python versions | pyenv, conda | mise use python@3.12 |
| Go versions | goenv, manual | mise use go@1.25 |
| CLI tools | Homebrew | mise use jq ripgrep bat |
| Per-project versions | .nvmrc + .python-version + ... | Single .mise.toml |
Benefits:
- •1000+ tools available (
mise registry | wc -l) - •Parallel installs, prebuilt binaries
- •Works on macOS and Linux
- •Declarative config in repo = reproducible environments
Quick Start
Install Mise
curl https://mise.run | sh
Shell Setup
Add to your shell rc file. If using both mise and direnv (recommended), load mise first:
# ~/.zshrc - recommended order eval "$(mise activate zsh)" eval "$(direnv hook zsh)" # fish: ~/.config/fish/config.fish mise activate fish | source direnv hook fish | source
For faster startup, use shims instead of (or with) activation:
# zsh: add to ~/.zshrc export PATH="$HOME/.local/share/mise/shims:$PATH" eval "$(mise activate zsh)" eval "$(direnv hook zsh)" # fish: add to ~/.config/fish/config.fish fish_add_path -p ~/.local/share/mise/shims mise activate fish | source direnv hook fish | source
Install Tools
mise use node@22 python@3.12 go@latest # Current directory mise use -g jq ripgrep bat # Global (all directories)
Project Setup
New Repo
# Pin language versions mise use node@22 go@1.25 # Creates .mise.toml - commit it git add .mise.toml
Existing Repo (first clone)
mise trust # Allow repo's .mise.toml mise install # Install pinned tools
Example .mise.toml
[tools] node = "22" go = "1.25" python = "3.12" # Project-specific tools just = "latest" sqlc = "latest"
Configuration Hierarchy
Mise merges configs from multiple levels:
~/.config/mise/config.toml # Global defaults
└── ~/projects/.mise.toml # Workspace defaults
└── ~/projects/foo/.mise.toml # Project-specific
More specific configs override less specific ones.
Global Config (~/.config/mise/config.toml)
Your daily-driver tools:
[tools] # Languages node = "lts" python = "3.12" go = "latest" # CLI tools (replaces Homebrew) jq = "latest" yq = "latest" ripgrep = "latest" fd = "latest" bat = "latest" eza = "latest" delta = "latest" fzf = "latest" gh = "latest" lazygit = "latest" just = "latest" direnv = "latest" starship = "latest" [settings] auto_install = true
Direnv Integration
Direnv handles per-directory environment variables. Combined with mise:
- •Mise → tool versions (node, go, python)
- •Direnv → environment variables (DATABASE_URL, API keys)
Best Practice: Keep a single source of truth:
- •
.mise.toml→ tool versions only (node, go, python)- •
.envrc→ environment variables (DATABASE_URL, API_KEY, etc.)Don't use
[env]section in.mise.toml- it creates confusion about where vars come from.
Setup
- •
Install direnv via mise:
bashmise use -g direnv
- •
Add direnv hook to shell rc:
bash# zsh eval "$(direnv hook zsh)" # fish direnv hook fish | source
- •
Create
.envrcin your project:bash# Load mise tools for this directory if command -v mise &> /dev/null; then eval "$(mise hook-env -s bash)" fi # Project-specific environment export DATABASE_URL="postgres://localhost/myapp" export LOG_LEVEL="debug"
- •
Allow the envrc:
bashdirenv allow
Tip: Use .envrc.example (committed) + .envrc (gitignored with secrets).
Just Integration
just and mise complement each other:
- •mise → pins tool versions
- •just → runs commands using those tools
See the just-pro skill for full patterns. Quick summary:
Shell Override (recommended for teams)
# Every recipe runs through mise automatically
set shell := ["mise", "exec", "--", "bash", "-c"]
build:
npm run build
test:
go test ./...
Graceful Degradation (for open source)
_exec cmd:
#!/usr/bin/env bash
if command -v mise &>/dev/null; then
mise exec -- {{cmd}}
else
{{cmd}}
fi
build: (_exec "npm run build")
Setup Recipe
setup:
#!/usr/bin/env bash
mise trust && mise install
# Create .envrc from example if missing
if [[ ! -f .envrc ]] && [[ -f .envrc.example ]]; then
cp .envrc.example .envrc
echo "Created .envrc from example - edit with your values"
direnv allow
fi
echo "Toolchain ready"
Mise vs Devcontainer
| Aspect | Mise + Direnv | Devcontainer |
|---|---|---|
| Isolation | Shared host filesystem | Full container isolation |
| Speed | Native performance | Container overhead |
| Setup time | Seconds (mise install) | Minutes (image build) |
| Works offline | After first install | After first build |
| IDE support | Any editor, native | VS Code / JetBrains |
| Team adoption | Low friction | Requires Docker knowledge |
| CI parity | Good (mise in CI) | Excellent (same container) |
Recommendation: Use mise for fast local dev. Add devcontainer for hermetic reproducibility if needed. They're not mutually exclusive.
Common Tools Available
mise registry | grep <tool> # Search for a tool
| Category | Tools |
|---|---|
| Languages | node, python, go, rust, java, ruby, php, elixir, zig |
| JSON/YAML | jq, yq, gojq |
| Search | ripgrep, fd, fzf, ag |
| Git | gh, lazygit, delta, git-cliff |
| Files | bat, eza, tree, dust, duf |
| HTTP | httpie, curlie, xh |
| Containers | kubectl, helm, k9s, docker-compose |
| Cloud | awscli, terraform, opentofu, pulumi |
| Dev | just, make, watchexec, hyperfine |
| Editors | neovim, helix |
Migration from Homebrew
Keep in Homebrew:
- •
git(system integration) - •Your shell (
fish,zsh) - •GUI apps (casks)
- •System utilities (
coreutilsif needed)
Move to mise:
- •Language runtimes (node, python, go, rust)
- •CLI dev tools (jq, ripgrep, bat, etc.)
- •Cloud CLIs (awscli, kubectl, terraform)
# Check what mise can replace brew leaves | while read pkg; do mise registry | grep -q "^$pkg " && echo "✓ $pkg" done
Troubleshooting
Tools not in PATH
mise doctor # Check activation status
Ensure mise activates after other PATH modifications in shell rc.
Shims vs Activate
- •Shims (
~/.local/share/mise/shims): Wrapper scripts, always work - •Activate: Dynamic PATH modification, faster for frequent version switching
Use both for reliability:
# Shims first (fallback), then activate (dynamic) export PATH="$HOME/.local/share/mise/shims:$PATH" eval "$(mise activate zsh)"
Direnv + Mise
Use mise hook-env -s bash in .envrc, not mise activate:
eval "$(mise hook-env -s bash)" # Correct eval "$(mise activate bash)" # Wrong - generates shell hooks
Quick Reference
# Install tools mise use node@22 # Current directory mise use -g ripgrep # Global # Manage versions mise ls # List installed mise ls-remote node # Available versions mise outdated # Check for updates # Project setup mise install # Install from .mise.toml mise trust # Trust current directory's config # Maintenance mise prune # Remove unused versions mise reshim # Rebuild shims mise self-update # Update mise itself
Further Reading
- •Mise Documentation
- •Mise Registry - All available tools
- •Direnv Documentation