docs/bootstrap.md
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.
mise bootstrap runs these steps in order:
mise bootstrap packages install installs missing [bootstrap.packages].mise dotfiles apply applies [dotfiles].mise bootstrap macos-defaults apply writes [bootstrap.macos.defaults].mise bootstrap launchd apply writes and loads [bootstrap.macos.launchd.agents].mise bootstrap systemd apply converges [bootstrap.linux.systemd.units]
by writing unit files, enabling/disabling them, and starting/stopping them
as configured.mise bootstrap user apply applies [bootstrap.user].mise install installs missing [tools].mise run bootstrap runs a task named bootstrap, 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.
[bootstrap.packages]
"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 --yes
For a dry run:
mise bootstrap --dry-run
By 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.
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 status
mise 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.
| Config | Use for |
|---|---|
[bootstrap.packages] | OS packages from 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 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.
mise trust
mise bootstrap --yes
mise bootstrap packages use apt:libssl-dev
This writes [bootstrap.packages] and installs what is missing.
$EDITOR ~/.zshrc
mise dotfiles add ~/.zshrc
mise dotfiles add stores the live file under dotfiles.root and writes an
explicit [dotfiles] entry with mode.
mise dotfiles edit ~/.zshrc
mise dotfiles apply ~/.zshrc
For symlinked dotfiles, edit opens the managed source, so it works with the
default symlink mode.
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.