Install mm
Install mm with one command on Linux or macOS, or build it from a checkout.
Run the installer on Linux or macOS:
curl --proto '=https' --tlsv1.2 -LsSf https://get.modmon.org/install.sh | sh
The installer downloads the release for your platform, checks the archive's size
and SHA-256 against the release manifest, and installs mm in ~/.local/bin.
It does not use sudo and does not edit shell startup files. If the install
directory is not on your PATH, the installer prints the directory to add.
It needs curl or wget, tar, and one of sha256sum, shasum, or openssl.
Releases are built for x86-64 and ARM64. See
supported platforms for what each runtime
and operating system supports.
Check the result, then set up local trust and shared services:
mm --version
mm dev setup
mm dev setup shows its plan and asks before it changes anything. Run mm
without arguments for the guided interface.
Installer options
Pass options after sh -s --:
curl --proto '=https' --tlsv1.2 -LsSf https://get.modmon.org/install.sh | sh -s -- --with-daemon
| Option | Effect |
|---|---|
--version vMAJOR.MINOR.PATCH | Install that exact release instead of the latest |
--install-dir PATH | Choose a different binary directory |
--with-daemon | Also install mmd; does not start a service |
--force | Allow a downgrade, or replace an mm whose version is unknown |
--no-modify-path | Do not print the PATH reminder |
To update, run the installer again. It replaces the existing binary only after
the new one is verified, and refuses to downgrade without --force.
Build from a checkout
To build mm yourself, clone the repository and run this from its root:
./install.sh
The installer builds mm and its dashboard, installs the binary in
~/.local/bin, sets up your shell's PATH, and runs workstation setup: wildcard
.test DNS, a trusted local certificate authority, and a reviewed shared ingress,
dashboard, and Mailpit service plan. Setup prompts for sudo and plan approval.
You do not need to install Rust or Node.js yourself.
Requirements
| Platform | What you need |
|---|---|
| Linux, x86-64 or ARM64 | Local Docker with Compose, systemd, active systemd-resolved, and sudo access |
| macOS, Intel or Apple Silicon | Apple Command Line Tools; for setup, Docker Desktop as the active Docker context, Homebrew, and sudo access |
On macOS, install the command-line tools first if needed:
xcode-select --install
Wait for that installation to finish, then run ./install.sh again.
Linux setup installs missing dnsmasq and haproxy packages using DNF, APT, or
Pacman. Other distributions need those packages installed beforehand. Existing
working wildcard .test resolution is reused. Otherwise setup installs the
dedicated mm-test-dns service and host-only DNS interface, routing only .test
queries there. It does not add per-project entries to /etc/hosts.
macOS setup installs missing haproxy and dnsmasq formulae with Homebrew as
your user. Local HTTPS on macOS is supported with Docker Desktop only: if
docker context show names another engine, such as Colima, run
docker context use desktop-linux before setup and when running mm. Existing
working wildcard .test resolution is reused. Otherwise setup adds
/etc/resolver/test, which sends only .test queries to a dnsmasq instance on
the dedicated loopback address 127.0.77.53. That dnsmasq runs from a
root-owned copy in /Library/PrivilegedHelperTools/, started by the
local.mm.dns and local.mm.loopback-alias launch daemons.
The default setup runtime is Docker Compose. If an existing installation uses
Podman Compose, run setup with MM_SETUP_RUNTIME=podman-compose instead; do not
switch the runtime owning shared services implicitly.
Privileges and binary ownership
The normal ~/.local/bin/mm remains owned by you and runs as your user. Setup
installs a separate, protected mm-ingress-helper under /usr/local/libexec/mm/.
That helper accepts only fixed, reviewed ingress installation/removal operations;
it is neither setuid nor a persistent root mm process. Setup does not install a
passwordless sudo rule.
On SELinux-enabled hosts, the helper installs a fixed mm_ingress policy and
labels only the two target-specific backend TCP ports. This permits HAProxy's
domain transition under the hardened systemd unit and its connections to mm's
loopback backend. SELinux stays enforcing; setup does not enable the global
haproxy_connect_any boolean. Fedora may require the
policycoreutils-python-utils package, which workstation setup installs when
missing.
On macOS, setup uses sudo only for .test DNS and, if you choose it, system-wide
certificate trust. macOS lets any user listen on ports 80 and 443 on all
addresses, so HAProxy, the Compose autostart, and the dashboard run as your user
in launchd agents under ~/Library/LaunchAgents/ (local.mm.ingress-forwarder,
local.mm.ingress, and local.mm.dashboard). mm writes their configuration
to ~/Library/Application Support/mm/ and their output to ~/Library/Logs/mm/.
At login, local.mm.ingress retries every 15 seconds until Docker Desktop is
running, then starts the ingress containers. The dashboard listens only on
127.0.0.1. No mm-ingress-helper is installed on macOS.
Once the public-port forwarder is installed, ordinary site routing changes reuse
it when its ownership, fixed configuration, and availability still match. They
do not elevate the CLI. Changing or removing the host-level forwarding setup
requires sudo again. Rebuilds keep the CLI user-owned; --setup-only refreshes
the protected helper when necessary.
As its last step, setup registers the mm MCP server with Claude Code and
OpenCode when they are on PATH, so their agents can
work on your projects. It changes only each tool's own user
configuration. --no-setup skips it; run mm agent setup --user --yes to do
it later, or for Codex and Cursor.
Use mm ingress stop to free public ports 80/443 for another development stack,
and mm ingress start to resume mm's public forwarding. These global commands
leave site containers and data running.
start also installs or repairs missing ingress prerequisites idempotently,
using the existing shared-services runtime and reviewed plans when needed.
The first build needs internet access, several GB of free disk space, and time to download and compile dependencies. Linux builds run in a Docker container limited to 1 CPU and 4 GiB RAM, without extra container swap. Docker must run on the same machine and CPU architecture; a remote Docker daemon is not supported.
On macOS, the installer downloads its own Rust and Node.js toolchains into a private user cache. Native Windows is not supported by this local installer.
Check it works
Open a new terminal, then run:
mm --version
mm --help
The installer configures Bash, Zsh, and Fish. For another shell, add the printed
install directory to PATH yourself. To use mm immediately without opening a
new terminal:
"$HOME/.local/bin/mm" --help
If your shell finds an older mm, use the PATH command printed at the end of
installation. command -v mm shows which copy your shell is using.
For Bash and Zsh, the installer also disables the legacy alias mm=./mm when
you reload the configured startup file or open a new terminal. To clear that
alias in an already-open session, run unalias mm, or use the current-session
command printed by the installer. Other custom aliases are preserved.
Update
From your checkout, get the latest changes and rerun the installer:
git pull
./install.sh
It builds the current checkout, including local edits, even when the package version has not changed. Downloaded dependencies and compilation results are cached to speed up later builds.
Normal installs use a fast incremental local build. For code-edit/rebuild cycles after workstation setup:
./install.sh --no-setup
The local profile disables link-time optimization and debug-info generation, retains incremental compilation, and caches optimized dependencies. Linux uses the Rust toolchain's LLD linker with one linking thread. The first build populates the cache; later builds reuse it. Unchanged build inputs reuse the cached binaries without rerunning npm or Cargo. Do not delete the cache to resolve an installer lock—it makes the next build cold again.
Use ./install.sh --release when you specifically want the slower, fully optimized
release build. Use --rebuild to force a build without deleting dependencies.
Linux builds remain limited to 1 CPU and 4 GiB RAM in either mode, and cannot use
extra swap. The default Cargo job count is one.
To finish or retry workstation setup without compiling anything:
./install.sh --setup-only
A setup failure leaves the successfully installed binary in place and reports
that workstation setup is incomplete. Fix the reported prerequisite and retry
--setup-only, rather than rebuilding.
If a previous shared-services operation left an interrupted journal, setup first offers a reviewed repair, then a fresh convergence plan. Neither setup plan purges data. Cancelling or leaving the service stack unhealthy keeps setup in an incomplete state instead of reporting success.
Options
| Option | Effect |
|---|---|
--install-dir PATH | Choose a different binary directory |
--no-modify-path | Leave shell startup files alone |
--no-setup | Install the binary without changing workstation DNS, trust, or services |
--setup-only | Set up the workstation and coding tools using the installed binary; skip compilation |
--dev | Select the fast incremental local build, which is the default |
--release | Build the slower optimized release binary |
--rebuild | Force a build instead of reusing an unchanged cached binary |
--with-daemon | Also build and install mmd; does not start a service |
--build-only | Write binaries under target/local-install/<target>/ without installing them into your normal binary directory or changing PATH |
--jobs NUMBER | Set parallel Rust compilation jobs; default is 1 |
--help | Show usage and requirements |
For example:
./install.sh --install-dir "$HOME/bin" --no-modify-path
Use a user-writable destination and run the installer without sudo.
If installation fails
- Docker unavailable: start Docker and check that
docker infoworks as your normal user, then rerun the installer. - Command Line Tools missing: complete
xcode-select --installon macOS. - Build uses too much memory: use the default local profile and one job. The Linux container is limited to 4 GiB with no extra swap.
- Another installer is running: the message identifies its PID and host. Locks with an exited local owner are recovered automatically. An ownerless lock is left alone until running installers have been checked; do not delete the dependency cache to clear it.
- Build error: inspect the build log path printed by the installer.
- DNS, trust, or ingress setup failed: rerun
./install.sh --setup-onlyfrom a terminal so sudo and the reviewed services plan can be approved. On Linux, inspectsystemctl status mm-test-dns.serviceandjournalctl -u mm-test-dns.servicefor DNS service failures. On macOS, check thatscutil --dnslists atestresolver using127.0.77.53, and inspectsudo launchctl print system/local.mm.dns. - macOS reports another Docker endpoint: local HTTPS on macOS needs Docker
Desktop. Run
docker context use desktop-linux, or unsetDOCKER_HOST.
The installer builds and checks the new binaries before replacing your installed
copy. It also writes install-receipt.txt beside the binaries with the source
revision and build toolchain versions.
The cache is ${XDG_CACHE_HOME:-$HOME/.cache}/mm-installer. You can delete it
when no installation is running; the next build will download dependencies again.
Uninstall
For the default destination:
rm "$HOME/.local/bin/mm"
If you installed the optional daemon, remove mmd from the same directory too.
You can also remove install-receipt.txt. For a custom destination, remove the
files there instead.
Removing binaries leaves running services, certificates, and private state in place. Use local service removal and TLS removal first if you want to remove those too.
If the installer created mm's dedicated DNS service, remove it separately:
sudo systemctl disable --now mm-test-dns.service
sudo rm /etc/systemd/system/mm-test-dns.service /etc/mm/dnsmasq.conf
sudo systemctl daemon-reload
Stopping that service removes its dedicated DNS interface. DNS provided by an existing resolver that setup merely reused is not changed.
On macOS, remove mm's DNS integration from the checkout with:
./scripts/install/local-dns-macos.sh --remove
It unloads local.mm.dns and local.mm.loopback-alias and removes only the
files mm created: /etc/resolver/test, the two launch daemons, the dnsmasq
configuration, and the dnsmasq copy. Removing the local services also removes
the ingress and dashboard launch agents.
On NetworkManager hosts, setup also installs
/etc/NetworkManager/conf.d/90-mm-test-dns.conf to keep other connection
dispatcher hooks from rewriting mm's DNS interface. Remove that mm-owned file
and run sudo nmcli general reload conf when removing mm's DNS integration.
After removing ingress, the protected helper can be removed from
/usr/local/libexec/mm/mm-ingress-helper; its user-owned build copy is beside
the CLI in the install directory.
Next step
Workstation setup prepares DNS, trust, and shared services. Project images,
environment values, and deployments are still project-specific. Bare mm guides
you through missing prerequisites. Run your first local app
for a complete example.