I started using mise to simplify both this repository and my Python projects. My goal was simple: reduce tool sprawl and make setup repeatable.
This is a beginner-friendly walkthrough of what worked for me on macOS.
What is mise?
mise is a tool manager and task runner. You can use it to:
- manage language/tool versions per project,
- keep setup in one file (
mise.toml), - run project commands with
mise run.
In this post, I separate it into two parts:
- how I use
misein this repository, - how I use it for Python (
uv) and how it replaced parts of my oldpyenv/toxflow.
I also use mise for general CLI tool management, not just Python. A real example from my setup:
[tools]
bitwarden-secrets-manager = { version = "0.5.0", version_prefix = "bws-v" }
cilium-cli = "0.19.2"
cilium-hubble = "1.18.6"
kubectl = "1.35.0"
talosctl = "1.12.5"
That gives me one place to pin infra/security tooling versions per machine or per project.
Why I started using it
Before mise, I was mixing multiple setup patterns across repos. The result was more context switching than I wanted.
mise gave me one place to pin tool versions and encode repeatable commands.
1) Install mise on macOS
Install:
brew install mise
Check that it works:
mise --version
Activate mise for your shell (zsh example):
echo 'eval "$(mise activate zsh)"' >> ~/.zshrc
source ~/.zshrc
If you use another shell, check the official docs for the activation command.
2) How I use mise in this repo
This repository already has a practical mise.toml you can use as a mental model:
[tools]
node = "22"
zola = "0.22.1"
gitleaks = "latest"
[tasks.deps]
description = "Install local lint dependencies"
run = "npm install --no-fund --no-audit"
[tasks."lint:md"]
description = "Lint markdown content"
depends = ["deps"]
run = "npx markdownlint-cli2 \"README.md\" \"content/**/*.md\" \"docs/**/*.md\""
[tasks."lint:scss"]
description = "Lint SCSS styles"
depends = ["deps"]
run = "npx stylelint \"sass/**/*.scss\" --max-warnings 0"
[tasks."scan:secrets"]
description = "Scan repository history for secrets"
run = "gitleaks git --redact --verbose"
[tasks.build]
description = "Build the site"
run = "zola build"
[tasks.check]
description = "Run all checks"
depends = ["scan:secrets", "lint:md", "lint:scss", "build"]
What this shows:
toolspins exact CLI versions.tasksmakes common commands reproducible.dependscomposes smaller tasks into one command (check).
Run the real workflow like this:
mise install
mise run check
Run namespaced tasks directly when needed:
mise run "lint:md"
mise run "lint:scss"
That is the setup I actually run here day to day.
Git-friendly team flow
For a new teammate (or your future self on another machine):
- clone repo
cdinto repomise trust(first time)mise installmise run check
That is the main reason I like this approach: setup is encoded in versioned files instead of personal shell history.
3) Separate Python workflow (mise + uv)
For Python projects, I start command-driven and let mise write the tool pins:
mkdir demo-python-mise
cd demo-python-mise
git init
mise use [email protected] uv@latest
mise install
python --version
uv --version
Then initialize dependencies:
uv init --name demo-python-mise
uv add requests
uv add --dev pytest ruff
uv sync
And commit the baseline:
git add mise.toml pyproject.toml uv.lock
git commit -m "chore: bootstrap python toolchain with mise + uv"
After that, I keep the same task-driven pattern as this repo.
For Python repos, keep it simple and task-driven:
[tools]
python = "3.12"
uv = "latest"
[tasks.setup]
run = "uv sync"
[tasks.test]
run = "uv run pytest -q"
[tasks.lint]
run = "uv run ruff check ."
[tasks.check]
depends = ["lint", "test"]
Then run:
mise install
mise run setup
mise run check
For non-uv repos, mise can still support classic virtualenv flows:
[env]
_.python.venv = { path = ".venv", create = true }What this replaced for me
In my day-to-day workflow, switching to mise + uv let me remove or reduce:
pyenvfor Python version switching,- most
toxusage for routine local test/lint flows, - ad-hoc shell notes for setup commands.
My default flow is now: mise use once, then mise install, then mise run ... / uv run ....
Before vs after
Before
- remember Python version setup manually,
- jump between different tooling patterns,
- repeat setup commands from memory or old notes.
After
- project declares Python version in
mise.toml, - same entry point across projects,
- easier repo switching and cleaner setup.
Common beginner gotchas
misecommand works, but Python version does not change.
Usually shell activation is missing. Make sure your shell rc includes:
eval "$(mise activate zsh)"
mise.tomlis not in the project root.
Run mise use ... inside the repository folder you want to configure.
- Forgetting to commit
mise.toml.
If mise.toml is not tracked by Git, your automated flow is only local.
- Mixing
uvflow and manual.venvflow in the same project.
Pick one default per repo. If you use uv, prefer uv run and uv sync. If you use classic venv, configure _.python.venv or create .venv manually.
- You get a trust prompt for
mise.toml.
This is expected for security. If the config is yours, run:
mise trustFinal thoughts
If you are just getting started and want a cleaner Python workflow, mise is worth trying. You do not have to migrate everything at once.
Start with one project, pin Python, add one or two tasks, and build from there.
I am also still finding new ways to use mise and continuously refining this workflow as I learn what fits my projects best.