Deployment guide · rapid-mlx service

Run at boot on a headless Mac

One command registers Rapid-MLX as a system LaunchDaemon, so a Mac used as an inference appliance serves your model after every boot — before anyone logs in — and restarts the server if it exits.

$ sudo rapid-mlx service install --service-user serveuser \
    --model qwen3.5-4b-4bit --host 127.0.0.1 --port 8000
$ rapid-mlx service status

serveuser is a dedicated, non-administrator account that already has Rapid-MLX installed and the model downloaded — set that up first (step 1 below). Rehearse with --dry-run to print every step without changing anything.

What we tested. On an M4 Pro Mac mini running macOS 26.5.1, the daemon loaded a model and completed a request after a full reboot while the Mac stayed at the login window with zero logged-in users. KeepAlive restart and the stop-update-start workflow were exercised too.

Before you begin

FileVault matters. A LaunchDaemon starts without a GUI login only after the data volume is unlocked. After a cold boot, a FileVault-protected Mac can wait at the preboot unlock screen, where neither SSH nor Rapid-MLX can start. Decide your encryption and unattended-recovery policy deliberately.

Multi-interface Macs. If the Mac also has a private Ethernet or Thunderbolt link, check that the internet default route is still the one you expect (route -n get default) before installing or upgrading — otherwise downloads fail while SSH still works.

1. Prepare the service account

Log in as the service account (with a display attached) and run, without sudo:

$ curl -fsSL https://rapidmlx.com/install.sh | bash
$ export PATH="$HOME/.local/bin:$PATH"
$ rapid-mlx doctor
$ rapid-mlx serve qwen3.5-4b-4bit --host 127.0.0.1 --port 8000

Let the first download finish, check that the server answers, then stop it. The daemon runs as this account, so its HOME, virtual environment and ~/.cache/huggingface/hub are the ones you just prepared.

2. Install the service

From an administrator session:

$ sudo rapid-mlx service install --service-user serveuser \
    --model qwen3.5-4b-4bit --host 127.0.0.1 --port 8000 --dry-run
$ sudo rapid-mlx service install --service-user serveuser \
    --model qwen3.5-4b-4bit --host 127.0.0.1 --port 8000

install validates the account, writes a root-owned plist to /Library/LaunchDaemons/com.rapidmlx.server.plist and bootstraps it into the system domain. The plist only runs a stable service run --config … command; the real definition is a versioned, root-owned JSON file under /Library/Application Support/Rapid-MLX/Services/, outside the Python environment, so rebuilding the venv leaves it untouched. It refuses to embed a secret, and refuses to install if a server already answers on the target port.

Pass extra non-secret serve flags after --, for example -- --max-num-seqs 4. Use the service's own --host and --port for binding.

Day-to-day

$ rapid-mlx service status                 # registration, pid, health, model residency
$ rapid-mlx service status --json          # the same, machine-readable
$ rapid-mlx service logs --follow          # stream logs across restarts
$ sudo rapid-mlx service restart           # kickstart and wait until healthy

Logs rotate automatically: 100 MiB per stream, five backups and seven days by default (change them with service configure --log-max-mb, --log-backup-count and --log-retention-days).

Change the model or flags

Changes are a two-step transaction. configure validates and stages a candidate without touching the running server; apply starts it, requires /readyz to report the configured model loaded, and restores the previous definition if anything fails.

$ sudo rapid-mlx service configure --model qwen3.5-9b-4bit --port 9000 -- --max-num-seqs 8
$ sudo rapid-mlx service apply --dry-run
$ sudo rapid-mlx service apply
$ rapid-mlx service config                 # active and pending definitions

configure --clear-serve-args removes all extra serve flags. Changing the service account or executable needs uninstall + install.

Keep the endpoint up without pinning memory. Pass the standby policy through to serve; the first request reloads the configured model:

$ sudo rapid-mlx service configure --model qwen3.5-9b-4bit -- \
    --lazy-load --idle-unload-seconds 1800
$ sudo rapid-mlx service apply

In standby /readyz stays green; /health and service status show whether the weights are loaded. A request can only wake the model fixed in the service definition — it cannot pick another repository or start a download.

Keep the endpoint private

The service binds to 127.0.0.1. Reach it from another computer through an SSH tunnel:

$ ssh -L 8000:127.0.0.1:8000 serveuser@your-mac

To require an API key, send it over stdin into a mode-600 credential file — it never appears in arguments, the plist, shell history or status output:

$ security find-generic-password -w -s rapid-mlx-api-key | \
    sudo rapid-mlx service credential set
$ sudo rapid-mlx service restart
$ rapid-mlx service credential status

sudo rapid-mlx service credential unset plus a restart turns authentication off. For LAN or internet access, keep Rapid-MLX on loopback behind a separately managed TLS-terminating, authenticating reverse proxy. Have the proxy send a loopback Host header to Rapid-MLX, or allow its forwarded Host with --trusted-hosts. If a browser frontend uses a different origin, allow that origin with --cors-origins.

Upgrade with automatic rollback

$ sudo rapid-mlx service upgrade --dry-run
$ sudo rapid-mlx service upgrade --extras vision,embeddings

upgrade freezes the working environment, upgrades as the service account, runs doctor, and accepts the new version only when launchd's /readyz reports the configured model loaded. Otherwise it restores the frozen environment and restarts the previous service. --extras re-asserts the optional extras the appliance needs; --version pins an exact release.

Verify recovery

$ curl -fsSLo headless_service_smoke.sh \
  https://raw.githubusercontent.com/raullenchai/Rapid-MLX/main/scripts/headless_service_smoke.sh
$ chmod +x headless_service_smoke.sh
$ export RAPID_MLX_SERVICE_USER=serveuser
$ export RAPID_MLX_SMOKE_MODEL=default
$ ./headless_service_smoke.sh

The smoke test checks the system-domain job, process owner, liveness, readiness, model inventory and a real one-token completion, waiting up to 120 seconds for readiness. For an authenticated service, export RAPID_MLX_API_KEY first. Then turn automatic login off, reboot with recovery access available, leave the Mac at the login window, and run it again remotely.

For desktop Macs that must come back after a power cut:

$ pmset -g custom
$ sudo pmset -a sleep 0 autorestart 1

This requests a restart when AC power returns; it does not bypass FileVault. If outage recovery is a hard requirement, run a controlled power-loss test at the site.

Remove the service

$ rapid-mlx service uninstall --dry-run
$ sudo rapid-mlx service uninstall

uninstall boots the daemon out and removes the plist. The service account, model cache, Python environment and logs stay.

The engine's headless service guide also documents the manual LaunchDaemon setup the CLI automates, for recovery or custom deployments.