Environments & backends¶
A pin that's worked for months suddenly fails — but only on the teammate running Windows, and only
for one tool. The version number in mise.toml hasn't changed; what differs is which of mise's nine
backends does the installing, and that backend's own constraints — a shell-script plugin, a Windows
gap, a stale cache — not the pin itself. course.md §7 lists the nine backends in a table and §8 shows
one flat [env] block; this page covers the two things left as loose ends: mise's
named-environment feature (MISE_ENV, which loads an extra mise.<profile>.toml file — dev,
staging, prod — layered on top of the base config, not just by directory), and what actually happens
when a backend installs a tool, so "why did this pin fail on a fresh machine" has a concrete answer.
TL;DR — in 30 seconds:
MISE_ENV(or--env/-E) makes mise also load a separatemise.<profile>.tomlfile (plus its.localvariant), merged on top of the basemise.toml— a different axis from the directory walk-up (course.md §8), which picks which directory's config files get merged in the first place.- A
mise.<profile>.tomlfile merges key-by-key onto the basemise.toml— a profile file that sets only one key still inherits every other key from the base. - For a short-form pin like
terraform = "1.7.0", mise's registry decides which of the nine backends installs it; the backend also decides a trust boundary (asdf/vfoxrun a plugin's code) and a speed boundary (core/aquajust download a binary).
1. Two different things both called "environment"¶
mise overloads the word. Keep them apart:
| Kind | What selects it | What it changes |
|---|---|---|
| Directory environment (course.md §8) | Your current working directory | Which directory's mise.toml files get merged (the walk-up) |
| Named environment / profile (this page) | The MISE_ENV variable (or --env) |
Which additional mise.<profile>.toml file gets merged on top of the base config |
You can be in the same directory all day and still flip between dev and production config by
changing MISE_ENV — no cd involved.
2. Declaring a profile-specific override¶
mise looks for an environment-suffixed file alongside the base mise.toml, named
mise.<profile>.toml — a separate file, not a section inside the base one:
# mise.toml — base, always loaded
[env]
LOG_LEVEL = "debug"
API_URL = "http://localhost:8080"
# mise.production.toml — only loaded when MISE_ENV=production
[env]
LOG_LEVEL = "warn"
API_URL = "https://api.example.com"
# mise.staging.toml — only loaded when MISE_ENV=staging
[env]
API_URL = "https://staging.example.com"
With MISE_ENV unset, only mise.toml is read. Set MISE_ENV=production and mise also reads
mise.production.toml, merging it onto the base key-by-key — LOG_LEVEL and API_URL both flip,
nothing else in the base [env] is disturbed. mise.staging.toml only sets API_URL, so LOG_LEVEL
still falls back to the base debug. Full precedence, highest wins: mise.{MISE_ENV}.local.toml >
mise.local.toml > mise.{MISE_ENV}.toml > mise.toml.
flowchart LR
B["mise.toml<br/>base file"] --> R["Resolved config<br/>for this MISE_ENV"]
P["mise.production.toml<br/>only when MISE_ENV=production"] -.override.-> R
S["mise.staging.toml<br/>only when MISE_ENV=staging"] -.override.-> R
A profile file's own [tools] and [tasks] tables follow the identical merge pattern — a
mise.production.toml can pin a different tool version or expose a different task set, not just
different env vars.
3. Setting the profile¶
MISE_ENV=production mise run deploy # one-off, this invocation only
export MISE_ENV=staging # rest of the shell session
mise env # confirm what's currently active
There's no ambient default profile beyond "base config only" — a CI job that must run as production
should set MISE_ENV explicitly rather than relying on a shell someone configured by hand.
4. What a backend actually does when it installs a tool¶
course.md §7's table names nine backends; the reason there are nine is that "install this tool" means something different depending on where the tool lives:
core— mise ships the installer logic itself (Node, Python, Ruby, Go, and similar widely-used runtimes). Fastest path, no external plugin to fetch first.asdf— delegates to an asdf plugin (a shell script following asdf's plugin API). Works for the long tail of tools asdf already supported; bash-based, so unix-only and generally the slowest path, but the widest existing catalog.vfox— delegates to a vfox plugin (Lua-based). Same idea as asdf's plugin model, but cross-platform, including native Windows.aqua— reads directly from the aqua catalog of GitHub-released CLI binaries. No plugin install step at all: aqua already knows the release asset naming for ~1500 tools, so it goes straight to "download the right asset for this OS/arch."ubi("universal binary installer") andpipx— narrower special cases:ubifor any GitHub release that roughly follows convention without needing a catalog entry,pipxfor Python CLIs that want their own isolated venv rather than sharing mise's Python.
For a short-form entry like terraform = "1.7.0", mise's registry is what decides which backend
handles it — you're not choosing. You only spell out a backend explicitly ("aqua:hashicorp/terraform")
when you want to override that default, most often because the registry's usual choice is broken for
your platform.
mise registry terraform # which backend does the short name resolve to?
mise install terraform@1.7.0 # actually fetch + extract it via that backend
5. Why the backend matters beyond "does it install"¶
The backend also decides plugin trust and speed, not just the fetch mechanism:
- An
asdf:/vfox:plugin is a piece of code (shell or Lua) that runs on your machine — the same trust boundary as themise trustgate onmise.tomlitself (course.md §10) applies to installing an untrusted plugin. coreandaquainstalls are just "download this binary" — no plugin script executes, so there's less to trust and less to go slow.
That's the exact failure from the top of this page: when a tool install is unexpectedly slow or
fails only for one teammate, mise registry <tool> and checking which backend is in play is the
first diagnostic step — an asdf: plugin failing on a Windows machine, or a stale plugin cache,
explains a lot of "works on my machine" installs that a core/aqua tool wouldn't hit.
Common gotchas
- Trying to set
MISE_ENVinsidemise.tomlitself. Fix: it can't work — mise has to know which environment is active before it decides which config files to load, soMISE_ENVmust come from the CLI (--env/-E), a shell variable, or an earlier-loading file — never from the config file whose loading it's supposed to control. - Assuming a profile file replaces the base entirely. Fix: it merges key-by-key — a profile
file that overrides only one key (like
mise.staging.toml'sAPI_URL) still falls back to the base for every other key (LOG_LEVELstaysdebug). - Relying on a shell someone configured by hand to set
MISE_ENVin CI. Fix: there's no ambient default profile beyond "base config only" — a CI job that must run asproductionshould setMISE_ENVexplicitly. - Assuming you choose which backend installs a tool. Fix: for a short-form entry like
terraform = "1.7.0", mise's registry decides the backend for you; you only override it explicitly ("aqua:hashicorp/terraform") when the default is broken for your platform. - Treating every backend as equally trustworthy and fast. Fix:
asdf/vfoxbackends run a plugin's code (shell or Lua) on your machine — the same trust boundary as themise trustgate;core/aquabackends just download a binary, so there's less to trust and less to go slow.
Check yourself — base mise.toml has LOG_LEVEL = \"debug\" and API_URL = \"http://localhost:8080\"; mise.staging.toml overrides only API_URL. You set MISE_ENV=staging. What's LOG_LEVEL?
Still debug. A profile file merges key-by-key on top of the base — mise.staging.toml only sets
API_URL, so LOG_LEVEL falls back to whatever the base mise.toml set.
Check yourself — a terraform pin that's worked for months suddenly fails, but only on the teammate running Windows. What's the first diagnostic step, and why?
Run mise registry terraform to see which backend installs it. An asdf: plugin is a bash script —
unix-only — so it failing on Windows explains a "works on my machine" install that a core/aqua
backend (just downloading a binary, no plugin script) wouldn't hit.
You can defend this when you can explain the difference between changing MISE_ENV and changing
directory; write a mise.production.toml override file and say which keys it changes versus inherits
from the base mise.toml; and, given a tool's short name, say which backend installs it and why that
choice trades off speed, platform support, or plugin trust differently from the alternatives.
Go deeper: the full named-environment spec is in the mise config environments docs, and each backend's own install mechanics and options are in the mise backend architecture docs; this page covers the 80/20 you need for the Do exercise.
6. How this connects¶
- §1's
MISE_ENV-vs-directory split is exactly the distinction Config hierarchy & precedence (this course's first page, §5) already names reciprocally — that page's own "How this connects" section says the two "environment" concepts answer different questions; this page is where each half gets spelled out. - §2's profile-specific
mise.production.toml[tasks]override is exactly what Tasks (this course's second page, §6) already names reciprocally — a named profile file can swap in a different task set, layered on top of that page's dependency graph, not a change to the graph itself. - §5's asdf/vfox plugin-trust boundary is a second, separate trust gate from the one Config hierarchy &
precedence §3 covers: that page's
mise trustprompt vets a config file once; installing an asdf/vfox-backed tool runs a plugin's code with no equivalent one-time gate — the closest analog elsewhere is Ansible Galaxy (module 08, Ansible, "Roles & collections" §6), where installing a community role or collection is the same "someone else's code, no built-in gate, read it first" trade-off.