Designing Your Workflow
You’ve met the pieces — tools, env, tasks, hooks. This page assembles them into a workflow a whole team can share: one committed config, a one-command onboard, a clear trust boundary, and a monorepo layout that scales.
The goal is boring in the best way — every dev and every CI runner gets the identical toolchain, env, and tasks from one source of truth.
flowchart TD repo["Git repo<br/>mise.toml + mise.lock committed<br/>*.local.toml gitignored"] repo --> d1["Dev A laptop"] repo --> d2["Dev B laptop"] repo --> ci["CI runner"] d1 --> bootstrap["mise trust → mise install --locked → mise run setup"] d2 --> bootstrap ci --> bootstrap bootstrap --> same["Identical tools + env + tasks everywhere"]
What to commit (and what not to)
Section titled “What to commit (and what not to)”The rule: commit the shared truth, gitignore the personal overrides.
Directorymy-app/
- mise.toml commit — tools, env, tasks for everyone
- mise.lock commit — exact versions + checksums (see Tools & Backends)
- mise.local.toml gitignored — your machine-only overrides
- .gitignore
# personal, machine-specific overrides — never shared*.local.toml[settings]lockfile = true # keep mise.lock in sync on every install/uselocked = true # plain `mise install` is strict — fail on missing platform URL
[tools]node = "22"python = "3.13"
[env]DATABASE_URL = { default = "postgres://localhost:5432/myapp_dev" }
[tasks.setup]description = "First-time project setup"run = ["npm install", "npm run db:migrate"]A dev who wants Node 22.14 specifically, or a different local DB, drops a
mise.local.toml — it wins over mise.toml but never leaves their machine.
(Full precedence table in Environments & Secrets.)
The 30-second onboard
Section titled “The 30-second onboard”Replace the README’s “first install nvm, then pyenv, then…” wall with one script.
#!/usr/bin/env bashset -euo pipefail
# 1. install mise if it's missingcommand -v mise >/dev/null || curl https://mise.run | shexport PATH="$HOME/.local/bin:$PATH"
# 2. trust THIS repo's config (it can execute code — see below)mise trust
# 3. install the exact pinned toolchainmise install --locked
# 4. run project setup (deps, migrations, …)mise run setupgit clone git@github.com:acme/my-app && cd my-app./scripts/bootstrap.shThat’s the whole onboarding doc. New laptop to running app in one command, and every step is reproducible from the committed config.
The trust security model
Section titled “The trust security model”A mise.toml can run arbitrary code — env templates, _.source scripts, hooks,
templated tasks. So when you cd into a repo you cloned, mise will not execute
any of that until you’ve trusted the config. Plain [tools] and non-templated
tasks load without prompting; anything that can run code is gated.
stateDiagram-v2 [*] --> Untrusted Untrusted --> Trusted: mise trust Trusted --> Untrusted: mise trust --untrust Untrusted --> Ignored: mise trust --ignore note right of Untrusted code-executing config (env templates, hooks, _.source) is NOT run end note
mise trust # trust the config in/above the current dirmise trust -a # trust everything (use sparingly)mise trust --untrust # revokemise trust --show # what's trusted and whymise doctor # lists untrusted configs it foundTrust state lives in ~/.local/state/mise/, keyed by path — trusting one repo
says nothing about another. For a monorepo, monorepo_root = true means trusting
the root also trusts its descendant configs.
Knobs worth knowing:
| Setting | Effect |
|---|---|
trusted_config_paths = ["/"] | trust everything under a path (e.g. all of ~/work) |
MISE_PARANOID=1 | paranoid mode — stricter trust checks, re-confirm more aggressively |
The task-driven dev loop (goodbye Makefile)
Section titled “The task-driven dev loop (goodbye Makefile)”Your Makefile / wall of npm scripts becomes [tasks] in the same file that
pins your tools — so a task always runs with the right toolchain on PATH. Full
task mechanics live in The Task Runner; here’s the team shape:
[tasks.setup]description = "One-time setup after clone"run = ["npm ci", "mise run db:migrate"]
[tasks.dev]description = "Run the app with reload"run = "npm run dev"
[tasks.test]description = "Run the suite"run = "npm test"
[tasks.lint]run = "eslint ."
[tasks.check]description = "What CI runs"depends = ["lint", "test"]mise run dev # everyone's "start working" commandmise run check # same gate locally and in CImise tasks # discoverable — no Makefile archaeologyThe win over make: tasks are self-documenting (mise tasks), run under the
pinned tools automatically, and are the exact commands CI runs. No
“works-on-my-machine” because the toolchain isn’t pinned.
Monorepos
Section titled “Monorepos”mise’s monorepo support is stable in 2026 (formerly the experimental
experimental_monorepo_root). Declare a root and where the sub-project configs
live — automatic filesystem-walk discovery is deprecated, so list them
explicitly:
monorepo_root = true
[monorepo]config_roots = ["packages/*", "services/*"] # single-level glob; ** is NOT supported
[tools]# shared toolchain for the whole monoreponode = "22"Each config_root carries its own mise.toml and merges with the root. Then
address tasks from anywhere:
flowchart TD root["// (monorepo root)<br/>monorepo_root = true"] root --> api["services/api<br/>mise.toml"] root --> web["packages/web<br/>mise.toml"] api -. "//services/api:test" .-> t1["run api's test"] web -. ":build (from inside packages/web)" .-> t2["run web's build"] root -. "//...:lint" .-> t3["run lint in every project"]
| Address | Means |
|---|---|
//packages/web:build | absolute — the build task in packages/web |
:test | the test task in the current config root |
//...:lint | the lint task in every project, any depth |
//services/*:test | test in each matching project (name wildcard) |
mise run //...:lint # lint the whole monorepomise run //services/api:test # one project's testsmise run :dev # the current project's dev taskCross-project dependencies use the same addressing — a services/api task can
depends = ["//packages/shared:build"] and mise builds shared first.
A worked polyglot monorepo
Section titled “A worked polyglot monorepo”Node front-end, Python service, Go service, and Postgres client tooling — one repo, one toolchain truth.
Directoryshop/
- mise.toml root: monorepo_root, shared base
- mise.lock
Directorypackages/
Directoryweb/
- mise.toml Node app
Directoryservices/
Directorybilling/
- mise.toml Python service
Directoryinventory/
- mise.toml Go service
- docker-compose.yml the actual Postgres daemon
monorepo_root = true
[settings]lockfile = truelocked = true
[monorepo]config_roots = ["packages/*", "services/*"]
[tools]# language tooling shared across every projectnode = "22"
[tasks."db:migrate"]description = "Apply migrations against the docker-compose Postgres"run = "docker compose exec -T db psql -U postgres shop_dev -f /migrations/latest.sql"
[env]DATABASE_URL = { default = "postgres://localhost:5432/shop_dev" }[tools]node = "22""npm:pnpm" = "latest"
[tasks.dev]run = "pnpm dev"
[tasks.build]run = "pnpm build"
[tasks.test]run = "pnpm test"[tools]python = "3.13""pipx:uv" = "latest"
[env]_.python.venv = { path = ".venv", create = true }
[tasks.dev]run = "uv run uvicorn billing:app --reload"
[tasks.test]run = "uv run pytest"[tools]go = "1.23"
[tasks.dev]run = "go run ./cmd/server"
[tasks.test]run = "go test ./..."Now the whole repo answers to a handful of commands:
mise run //...:test # every project's tests, in parallelmise run //packages/web:dev # just the front-endmise run :test # current project (run from inside it)Where to go next
Section titled “Where to go next”- Wire this into CI and ship slim Docker images — CI, Docker & IDEs.
- Coming from
asdf/nvm/direnv/make? — Migrating off the old tools. - Deepen the task layer (incremental builds, args, watch) — The Task Runner.