Bootstrap experimental
mise bootstrap sets up the machine-level pieces around a mise config: OS packages, dotfiles, macOS defaults, macOS LaunchAgents, Linux systemd user services, the user's login shell, tools, and any final project-specific task. You can also add hooks that run at named points in the bootstrap sequence.
Use bootstrap for things that are needed before a project or workstation is ready, but that do not belong in [tools]: native libraries, Homebrew formulae, shell rc files, editor config, macOS preferences, user services, and one-time machine setup.
How it runs
mise bootstrap runs these steps in order:
mise bootstrap packages installinstalls missing[bootstrap.packages].mise dotfiles applyapplies[dotfiles].mise bootstrap macos-defaults applywrites[bootstrap.macos.defaults].mise bootstrap launchd applywrites and loads[bootstrap.macos.launchd.agents].mise bootstrap systemd applyconverges[bootstrap.linux.systemd.units]by writing unit files, enabling/disabling them, and starting/stopping them as configured.mise bootstrap user applyapplies[bootstrap.user].mise installinstalls missing[tools].mise run bootstrapruns a task namedbootstrap, if one exists.[bootstrap.hooks.final]runs after the bootstrap task, if configured.
Hook phases can also run before and after the built-in steps: pre-packages, post-packages, pre-dotfiles, post-dotfiles, pre-defaults, post-defaults, pre-user, post-user, pre-tools, and post-tools.
The declarative steps converge: if a package is already installed, a dotfile already matches, or a default is already set, mise skips it. The bootstrap task runs every time, so keep it idempotent.
Example
[bootstrap.packages]
"apk:build-base" = "latest"
"apt:build-essential" = "latest"
"brew:postgresql@17" = "latest"
[dotfiles]
"~/.gitconfig" = { mode = "symlink" }
"~/.config/nvim" = { mode = "symlink" }
"~/.zshrc/activate" = { block = 'eval "$(mise activate zsh)"' }
[bootstrap.macos.dock]
autohide = true
orientation = "left"
tilesize = 48
[bootstrap.macos.finder]
show_pathbar = true
[bootstrap.macos.keyboard]
key_repeat = 2
initial_key_repeat = 15
[bootstrap.macos.trackpad]
tap_to_click = true
[bootstrap.macos.defaults]
"com.apple.finder" = { AppleShowAllFiles = true }
[bootstrap.macos.launchd.agents.my-sync]
program = "~/.local/bin/my-sync"
args = ["--watch"]
run_at_load = true
[bootstrap.linux.systemd.units.my-sync]
description = "sync files"
exec_start = "~/.local/bin/my-sync --watch"
restart = "on-failure"
[bootstrap.user]
login_shell = "/bin/zsh"
[bootstrap.hooks.pre-packages]
run = "softwareupdate --install-rosetta --agree-to-license"
[bootstrap.hooks.post-defaults]
run = "killall Dock || true"
[tools]
node = "lts"
python = "3.12"
[tasks.bootstrap]
run = "gh auth status || gh auth login"Then run:
mise bootstrap --yesFor a dry run:
mise bootstrap --dry-runBy default, bootstrap refuses dotfile conflicts rather than replacing local files. Use mise bootstrap --force-dotfiles when you explicitly want the dotfiles phase to replace conflicting whole-file dotfile targets.
Inspecting State
Use the narrower commands when you want to inspect one part of the bootstrap state:
mise bootstrap packages status
mise dotfiles status
mise dotfiles apply --dry-run
mise dotfiles apply --dry-run --verbose
mise bootstrap macos-defaults status
mise bootstrap launchd status
mise bootstrap systemd status
mise bootstrap user statusmise bootstrap packages status --missing and mise dotfiles status --missing are useful CI checks when a repo expects machine setup to be in place but should not install anything during that check.
What Goes Where
| Config | Use for |
|---|---|
[bootstrap.packages] | OS packages from apk, apt, dnf, pacman, or brew |
[dotfiles] | Whole-file dotfiles and small managed edits to existing files |
[bootstrap.macos.*] | Curated macOS preferences for Dock/Finder/keyboard/trackpad |
[bootstrap.macos.defaults] | macOS user preferences written through defaults write |
[bootstrap.macos.launchd.agents] | macOS user LaunchAgents written and loaded with launchctl |
[bootstrap.linux.systemd.units] | Linux systemd user services managed with systemctl --user |
[bootstrap.user] | Current-user settings such as login_shell |
[bootstrap.hooks] | Commands that run at named bootstrap phases |
[tools] | Versioned dev tools managed by mise |
[tasks.bootstrap] | Anything custom that should run after tools are installed |
Use declarative sections when mise can inspect and converge the state. Use [tasks.bootstrap] for imperative setup that does not fit those sections, such as cloning a private repository, running an auth flow, or seeding local data.
Hooks
Hooks run only during explicit mise bootstrap invocations. A hook can be specified as a command string, an array of command strings, or a table with a run field. They use the same default inline shell setting as tasks, stop the bootstrap if they fail, and print the command instead of running it during mise bootstrap --dry-run. Hooks run in the current process environment; use mise exec -- ... inside a hook, or use [tasks.bootstrap], when the command needs tools from [tools] on PATH.
[bootstrap.hooks.pre-packages]
run = "softwareupdate --install-rosetta --agree-to-license"
[bootstrap.hooks.post-tools]
run = [
"mise exec -- corepack enable",
"mise exec -- rustup component add rustfmt clippy",
]
[bootstrap.hooks.final]
run = "gh auth status || gh auth login"As shorthand, a hook phase can also be set directly:
[bootstrap.hooks]
post-defaults = "killall Dock || true"Hooks merge across the config hierarchy from global to local, so shared config can define broad machine setup while a project adds its own phase commands.
Common Workflows
New Machine
mise trust
mise bootstrap --yesAdd A Package
mise bootstrap packages use apk:zlib-dev apt:libssl-devThis writes [bootstrap.packages] and installs what is missing.
Capture An Edited Dotfile
$EDITOR ~/.zshrc
mise dotfiles add ~/.zshrcmise dotfiles add stores the live file under dotfiles.root and writes an explicit [dotfiles] entry with mode.
Edit A Managed Dotfile
mise dotfiles edit ~/.zshrc
mise dotfiles apply ~/.zshrcFor symlinked dotfiles, edit opens the managed source, so it works with the default symlink mode.
Advanced: Self-Managing Config
You can manage the dotfiles repository and the mise global config as dotfiles:
[settings]
dotfiles.root = "~/.dotfiles"
[dotfiles]
"~/.dotfiles" = "~/src/dotfiles"
"~/.config/mise/config.toml" = "~/.dotfiles/mise/config.toml"The repo/source must exist before the first apply. Replacing the active global config affects future mise invocations, so use this pattern carefully.