docs/bootstrap/services.md
[bootstrap.services] declares services in two scopes:
scope = "user") are services mise defines for the
current user, declared once and installed on every platform: a systemd user
unit on Linux, a LaunchAgent on macOS, a Scheduled Task on Windows.[bootstrap.services.mise-history] # the built-in history watcher
builtin = "history-watch" # implies scope = "user"
[bootstrap.services.my-agent]
scope = "user"
command = "~/.local/bin/my-agent --serve"
description = "My agent"
restart = "on-failure" # "always" | "on-failure" | "never"
environment = { RUST_LOG = "info" }
working_directory = "~"
requires_tools = true # converge after [tools] are installed
One declaration is rendered for the platform's user service manager:
| platform | definition | manager |
|---|---|---|
| Linux | ~/.config/systemd/user/dev.mise.<name>.service | systemctl --user |
| macOS | ~/Library/LaunchAgents/dev.mise.<name>.plist | launchctl |
| Windows | Scheduled Task mise\<name> (definition kept under $MISE_STATE_DIR/user-services/) | schtasks |
command: the command line to run. ~ and ~/ are expanded. Required
unless builtin is set.builtin: a definition mise supplies. "history-watch" runs
mise bootstrap dotfiles watch through a durable mise executable with
restart = "on-failure" and a low priority. A builtin implies
scope = "user"; command cannot be combined with it.
To avoid a tight failure loop, Linux allows three starts within five
minutes; macOS spaces repeated launches at least five minutes apart.
If Linux stops retrying, fix the problem reported by mise doctor or
the service logs, then rerun mise bootstrap to reset its failure budget
and restart it. You can also reset and start it directly
(systemctl --user reset-failed dev.mise.<name>.service followed by
systemctl --user start dev.mise.<name>.service). Ordinary custom
services retain their existing restart behavior.description: shown by the service manager.restart: "on-failure" (default), "always", or "never". On Linux this
is Restart=; on macOS KeepAlive ({ SuccessfulExit = false } for
on-failure). Task Scheduler restarts only failed runs, so on Windows
"always" and "on-failure" both restart up to three times a minute apart
after a failure and run again at logon (when enabled = true); a clean
exit is not restarted. Strict "always" semantics are a Linux and macOS
feature; a service that must survive a clean exit on Windows should loop
inside its own program.environment and working_directory map directly to the platform
definition. On Windows, environment variables are set through cmd.exe,
so values containing characters it would reinterpret (%, ", &, |,
<, >, ^) are rejected, and so is a command containing %, &,
|, <, >, or ^ once environment is set (without environment
the command runs directly). Move such a command into a script, or set the
variables inside the program.state: "running" (default), "stopped" (installed but not running), or
"absent" (the installed definition is removed and stays removed while
declared so).enabled: whether the service starts at login (default true). On macOS
this is RunAtLoad, which launchd also honours when the agent is loaded,
so a stopped agent is written without it (it starts at login again once it
is set running). launchd reads any KeepAlive as run-at-load too, so an
agent with enabled = false is written without one: it is started once by
the apply but neither starts at login nor is restarted after a failure
until it is enabled again.requires_tools: converge in a second pass after [tools] and plugin
package managers, so a service that runs a tool starts after it exists. The
built-in watcher needs only mise and converges in the services step.Names must contain only letters, numbers, ., _, or -, and must not also
appear in [bootstrap.linux.systemd.units] or
[bootstrap.macos.launchd.agents]: both would write the same definition.
A builtin is written with an absolute path to the mise that installed it.
mise uses the running executable unless it lives in a temporary directory or
in the staging directory of mise bootstrap remote, and otherwise a mise
found on PATH outside those. When only a staged binary exists the service is
reported as unknown: no durable mise executable; install mise on this host first and is never written with a path that will be deleted.
state = "absent" removes the installed unit, agent, or task and keeps it
absent on later runs while declared so. Deleting the declaration leaves the
installed service in place until it is removed once:
mise bootstrap services remove my-agent
The next mise bootstrap recreates it if it is still declared.
mise bootstrap services status and mise bootstrap services apply cover
both scopes; mise bootstrap status and mise bootstrap plan list user
services as user-service:<name>. mise bootstrap status --json includes
each user service's rendered definition under user_services, so what mise
would install can be inspected before applying. When the platform's user service manager is unavailable (for
example, no systemd user manager in a container), user services are reported
as unknown and skipped with a follow-up note; nothing is written.
Fields that only apply to user services (command, builtin, description,
restart, environment, working_directory, requires_tools, and
state = "absent") are rejected on a system-scope entry, so a missing
scope = "user" cannot silently turn a service definition into a lookup of a
system unit. Managed-file notifications apply to system services only.
Package installation and [bootstrap.files] run first, so a service may be
installed by a package or supplied as a managed unit file. After file changes,
mise reloads systemd before applying service changes.
[bootstrap.packages]
"apt:docker.io" = "latest"
[bootstrap.services.docker]
state = "running"
enabled = true
Names without a unit suffix receive .service. Explicit unit names such as
[email protected], sockets, and timers are also accepted.
This section manages system units already supplied by packages or
managed files. A service that runs as your user is a
user service (scope = "user", above); hand-written user
units go through systemd user units.
Preview with mise bootstrap services apply --dry-run. If the unit will be
created by the same configuration, use the full bootstrap to install its package
or file before converging the service.
state: "running" (default) or "stopped"enabled: whether the unit starts at boot (default true)masked: whether systemd must prevent the unit from starting (default
false)on_change: action to take when a changed managed file or directory
notifies the service: "reload_or_restart" (default), "reload",
"restart", or "none"Managed files and directories can notify one or more services. Notifications
run only after a resource actually changes; dry runs show the same action. A
notification never starts or restarts a service declared state = "stopped";
on_change applies only while the desired service state is running.
[bootstrap.files."/etc/docker/daemon.json"]
content = '{ "log-driver": "local" }'
notify = ["docker"]
[bootstrap.services.docker]
state = "running"
enabled = true
on_change = "reload_or_restart"
Notification names are validated before any bootstrap mutation, so a typo
cannot leave a host partially provisioned. mise runs one daemon-reload,
re-inspects all affected units, and validates every action before changing any
service. A missing unit is retried only when the changed notification source is
that unit's managed file in a systemd system-unit search directory (including
an instantiated unit's [email protected] template). A notification from an
ordinary configuration file cannot make an unrelated missing unit appear and
therefore remains unknown. This allows a unit newly written by
[bootstrap.files] to be started safely without weakening fail-closed behavior.
Once an interactive user confirms a managed-file change, its notification
handlers run as part of that confirmed change; unrelated service drift remains
separately confirmable.
mise bootstrap services status and mise bootstrap services apply inspect
and converge service lifecycle state only. They do not synthesize a file
notification before its file has changed. Aggregate mise bootstrap status
and mise bootstrap plan include the notification consequences of pending
managed-file changes, while mise bootstrap files apply runs those handlers
only after the causal file operation succeeds.
Removing a service declaration leaves its current state unmanaged. To stop it and prevent future starts, keep an explicit declaration. A masked unit must also be stopped and disabled:
[bootstrap.services.old-worker]
state = "stopped"
enabled = false
masked = true
mise does not guess when a unit is missing, systemd is unavailable, or a unit
cannot be enabled (for example, a static unit). Status and plans report the
resource as unknown; apply fails closed instead of running an unsafe command.
mise bootstrap services status
mise bootstrap services status --json
mise bootstrap services apply --dry-run
mise bootstrap services apply --yes
System service management is Linux-only and requires root privileges. mise prompts through sudo only when a change is required.