$linuxjunkies
>

Use mise for Tools, Tasks and Env Vars

Replace asdf, direnv, and task runners with a single mise binary. Covers installation, tool versioning, env vars, tasks, and secrets via sops.

IntermediateUbuntuDebianFedoraArch9 min readUpdated June 7, 2026

Before you start

  • curl installed for the script-based install method
  • age or gpg available if using sops for secret encryption
  • A terminal with bash, zsh, or fish as the interactive shell
  • sudo or root access if installing via the system package manager

mise (pronounced "meez") is a single binary that replaces three tools at once: asdf for language runtime versioning, direnv for per-directory environment variables, and just/make for project task runners. If you have been juggling .tool-versions, .envrc, and a Makefile across multiple projects, mise consolidates all of it into one .mise.toml file. This guide walks through installation, migrating from asdf and direnv, defining tasks, and loading secrets with sops.

Install mise

The official install script drops a versioned binary into ~/.local/bin and works on any distro.

curl https://mise.run | sh

Then activate the shell hook. Add the appropriate line to your shell's RC file and reload it.

# bash – add to ~/.bashrc
eval "$(~/.local/bin/mise activate bash)"

# zsh – add to ~/.zshrc
eval "$(~/.local/bin/mise activate zsh)"

# fish – add to ~/.config/fish/config.fish
~/.local/bin/mise activate fish | source
source ~/.bashrc   # or restart your terminal

Verify the install:

mise --version
# mise 2024.x.x

Alternatively, install via your package manager on supported distros.

# Debian/Ubuntu (apt)
apt-get install -y gpg
curl -fsSL https://mise.jdx.dev/gpg-key.pub | gpg --dearmor \
  | sudo tee /usr/share/keyrings/mise-archive-keyring.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/mise-archive-keyring.gpg arch=amd64] \
https://mise.jdx.dev/deb stable main" \
  | sudo tee /etc/apt/sources.list.d/mise.list
sudo apt update && sudo apt install -y mise
# Fedora / RHEL family
sudo dnf install -y mise
# Arch (AUR helper)
yay -S mise-bin

Migrate from asdf

mise reads asdf's .tool-versions files natively, so existing projects keep working immediately. To upgrade a project to native mise format, run:

cd /path/to/project
mise migrate

This converts .tool-versions into .mise.toml. You can also do it manually — the mapping is direct:

# .tool-versions (asdf)
nodejs 20.12.0
python 3.12.3
terraform 1.8.1
# .mise.toml equivalent
[tools]
node = "20.12.0"
python = "3.12.3"
terraform = "1.8.1"

Install all pinned tools in the current project in one shot:

mise install

To add a new tool and immediately pin it:

mise use node@22        # pins to latest 22.x, writes to .mise.toml
mise use --global [email protected]  # writes to ~/.config/mise/config.toml

List what is currently active in the working directory:

mise ls --current

Replace direnv with mise Env Vars

direnv requires a separate daemon and a trusted .envrc. mise handles environment variables directly in .mise.toml with the same directory-scoped activation — no extra process needed.

# .mise.toml
[env]
APP_ENV = "development"
DATABASE_URL = "postgres://localhost/myapp_dev"
PATH = "{{env.PATH}}:./bin"   # extend PATH relative to the project root

When you cd into the directory the hook sets these variables; they are unset when you leave. Trust the file the first time (equivalent to direnv allow):

mise trust

If you have existing .envrc files, mise can import them directly by adding a single line to .mise.toml:

[env]
_.file = ".envrc"   # source the existing file as-is

The _.path key extends PATH without shell quoting hassles:

[env]
_.path = ["./node_modules/.bin", "./scripts"]

Define Tasks in .mise.toml

Tasks replace common Makefile or just targets. They run inside the project's mise environment, so the correct runtime versions are always on PATH.

# .mise.toml
[tasks.test]
description = "Run the test suite"
run = "pytest -v"

[tasks.lint]
description = "Lint Python source"
run = "ruff check src/"

[tasks.build]
description = "Build production assets"
run = [
  "npm ci",
  "npm run build",
]
depends = ["lint"]   # lint must pass first

Run a task:

mise run test
mise run build   # runs lint, then build

List all tasks defined in the project:

mise tasks

For more complex tasks, store them as standalone scripts in a mise-tasks/ directory. Any executable file there is automatically available as a task with the filename as its name.

mkdir -p mise-tasks
cat > mise-tasks/db-seed <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
python manage.py seed --env "$APP_ENV"
EOF
chmod +x mise-tasks/db-seed
mise run db-seed

Load Secrets via sops

Hard-coding secrets in .mise.toml and committing it is a security risk. The clean solution is sops — encrypt a .env-style file at rest, decrypt it at runtime.

Install sops

# Debian/Ubuntu
sudo apt install -y sops

# Fedora/RHEL
sudo dnf install -y sops

# Arch
yay -S sops

Create and encrypt a secrets file

This example uses age for encryption (simpler than GPG for local use).

# Generate an age key if you don't have one
mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
# Note the public key printed to stdout, e.g. age1ql3z...
# .sops.yaml in the project root
creation_rules:
  - path_regex: secrets\.env$
    age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
# Create and encrypt the secrets file
cat > secrets.env <<'EOF'
SECRET_KEY=supersecret
SENDGRID_API_KEY=SG.xxxxxxxxxxxx
EOF
sops --encrypt --in-place secrets.env

Commit secrets.env (now ciphertext) and .sops.yaml. Add the plaintext to .gitignore only as a fallback — sops won't let you accidentally store plaintext once the file is encrypted.

Wire sops into mise

Use _.file with sops's --decrypt flag via a sourced subprocess is not supported directly, but mise has a first-class sops integration:

# .mise.toml
[env]
_.sops = "secrets.env"   # mise decrypts on cd, sets vars in shell

mise calls sops --decrypt secrets.env automatically when the directory is activated. The SOPS_AGE_KEY_FILE environment variable must be set, or mise will look for ~/.config/sops/age/keys.txt by default.

# Confirm the secret is live after cding into the project
mise env | grep SECRET_KEY
# SECRET_KEY=supersecret

Verify the Full Setup

cd /path/to/project
mise doctor          # checks install, hooks, and config validity
mise ls --current    # shows active tool versions
mise env             # dumps all env vars mise is setting
mise tasks           # lists available tasks

mise doctor will flag missing shims, untrusted config files, or sops decryption failures explicitly — run it first whenever something behaves unexpectedly.

Troubleshooting

  • Tool not found after install: The shell hook is not active. Confirm eval "$(mise activate bash)" is in your RC file and you have reloaded the shell, not just the terminal tab.
  • mise trust prompt on every cd: The .mise.toml has changed since you last trusted it. Run mise trust again. In CI, set MISE_TRUSTED_CONFIG_PATHS=/path/to/project or pass --yes flags.
  • sops decryption fails: Check that SOPS_AGE_KEY_FILE points to a key file containing the private key matching the public key in .sops.yaml. Run sops --decrypt secrets.env manually to see the raw error.
  • Conflict with existing asdf shims: mise installs its own shims directory. Remove asdf from your PATH or uninstall it; the two shim directories will conflict. Run mise reshim after removal to rebuild cleanly.
  • Task runs with wrong Node/Python version: Ensure .mise.toml is in the project root, not a subdirectory, and that you have run mise install after editing the [tools] section.
tested on:Ubuntu 24.04Fedora 40Arch rollingDebian 12

Frequently asked questions

Does mise work in CI pipelines without the shell hook?
Yes. Use `mise exec -- <command>` to run a command with the correct tool versions on PATH without needing the interactive hook. Set MISE_TRUSTED_CONFIG_PATHS or pass --yes to skip trust prompts non-interactively.
Can I keep using asdf plugins with mise?
mise supports most asdf plugins through its registry. Run `mise use <plugin>@<version>` and mise will fetch the asdf plugin automatically if a native backend does not exist yet.
Is it safe to commit .mise.toml to version control?
Yes, .mise.toml is designed to be committed. Never put plaintext secrets in it directly — use the _.sops key or reference variables already in the environment with {{env.VAR_NAME}}.
How do I share a global tool version across all projects?
Use `mise use --global <tool>@<version>`, which writes to ~/.config/mise/config.toml. Project-level .mise.toml entries override global ones for the same tool.
What happens to env vars when I cd out of the project directory?
The shell hook automatically unsets variables defined in that directory's [env] block when you leave, the same behaviour as direnv. Variables defined at a parent directory level remain active until you leave that scope too.

Related guides