Running on macOS
On macOS,outerlayer runner init offers to run the runner in a small Linux
VM with Docker Engine. A Mac cannot give a build a container of its own, but
the VM can. Builds there run in containers and record container, as on a
Linux host. It works on Apple silicon and on Intel Macs.
Before you start:
- Lima, installed with
brew install lima. - A config file that holds
url,apiKeyandappId. Setup moves them into the VM. If you also useouterlayeron this Mac, put the runner key in a config file of its own and add--config <file>to each command below, so your own login in~/.outerlayer/config.jsonstays as it is. - An existing runner block moves into the VM, without its hooks. Setup
refuses a block whose hooks are anything other than
builtin:process. - Setup refuses when the config already has a
runnerVmnote, or a Lima instance namedouterlayer-runneralready exists.
--vm answers yes without asking, and --no-vm answers no. With neither, a
terminal gets the question and a script gets a refusal that names --vm.
What the VM needs
-
Lima.
outerlayer runner init --vmrunslimactl. It refuses withbrew install limawhenlimactlis not on yourPATH. If a step in the VM fails, the Mac’s config is unchanged; delete the half-built VM withlimactl delete --force outerlayer-runner. -
Room for the builds. With the default limits the VM gets 5 CPUs,
11 GiB of memory and 90 GiB of disk. The VM is sized from the runner’s
concurrencyandbuildlimits, and never gets more CPUs or memory than the Mac has.- CPUs: each build is pinned to
build.cpuscores of its own, rounded up. The VM gets that count timesconcurrency, plus one. - Disk: the sum of
build.diskfor every build but the last, the larger ofbuild.diskandhousekeeping.minFreeDisk, the Docker build cache’s cap, and 30 GiB for the OS and images. Lima allocates it as it is used. - Memory:
concurrencytimesbuild.memory, plus 2 GiB for the runner, divided by 0.95 and rounded up. A Linux guest reports less memory than it is given. - Setup makes no VM when the builds do not fit in the memory the VM will
report. On a Mac with too little memory, lower
runner.concurrencyorrunner.build.memory.
~/.lima/outerlayer-runner/lima.yaml, then runlimactl stop outerlayer-runnerandlimactl start outerlayer-runner. - CPUs: each build is pinned to
- A network. The first run downloads an Ubuntu image, Node and the CLI.
-
Your Claude credential, in the VM. It is not copied from the Mac. In
the VM, put
CLAUDE_CODE_OAUTH_TOKEN=<token>in~/.outerlayer/runner.env, runchmod 600 ~/.outerlayer/runner.env, and restart a running service:limactl shell outerlayer-runner sudo systemctl restart outerlayer-runner.
What lives where
The VM is a Lima instance namedouterlayer-runner, with no directory of the
Mac mounted, so a build cannot read your home directory. It holds the CLI,
the runner and host keys, and a systemd service, outerlayer-runner.
The Mac’s config loses apiKey and runner, and gains a runnerVm note.
If you use capture on the Mac, run outerlayer login there again with a key
that is not the runner key. A runner key bound to an old Mac host key is
refused until a member uses Clear host key on the API keys page.
A launch agent, ai.outerlayer.runner-vm
(~/Library/LaunchAgents/ai.outerlayer.runner-vm.plist), runs
limactl start outerlayer-runner when you log in.
Enable the service
outerlayer runner init --vm enables and starts the service for you. It
runs outerlayer runner install --service inside the VM, which writes the
same unit as on Linux. You write no unit. The launch agent starts the VM at
login, and the VM starts the service. Before login, the runner does not
run.
With placeholder commands in the runner block, or no credential in
runner.env, the service runs but claims nothing. The host’s entry in the
factory says why. Edit the commands and restart the service:
outerlayer runner install --service on the Mac. It runs the same command
inside the VM and rewrites the launch agent. To remove the service and the
launch agent, run outerlayer runner uninstall --service on the Mac. See
Run a host as a service.
Upgrade and operate the VM
The runner in the VM updates itself to the CLI version your factory names. It does not follow upgrades of the CLI on the Mac. Setup leaves npm 9.5 or later in the VM, which self-update needs to verify a release’s provenance. See Keep the runner up to date. To upgrade by hand, install the new CLI in the VM, put it in the runner’s install layout, then restart the service:npm install --global alone does not upgrade the runner. The service
starts the CLI under ~/.outerlayer/runner/current, and only
runner install --service moves that link. Restart when no build is
running, or the restart interrupts it.
To try a CLI build that is not published, pack it with npm pack and give
the tarball to outerlayer runner init --vm --vm-cli-package <file>.
Run runner commands through limactl shell outerlayer-runner, for example
outerlayer runner status or outerlayer runner logs <item>. outerlayer doctor on the Mac reports the VM,
and says to run limactl start outerlayer-runner when it is stopped. It reads
only ~/.outerlayer/config.json and has no --config option. With the runner
key in a config file of its own, doctor does not see the VM, so use
limactl list to check that it is running.
A second factory on the same Mac
One Mac runs one VM, andouterlayer runner init --vm refuses to make a
second. The one VM can still run a runner for a second factory. Each runner
needs its own config file in a directory of its own, and its own service.
Do this inside the VM:
runner.commands in that file, put the Claude credential in
~/factory-b/runner.env, and restart outerlayer-runner-b. Give it a
runner key of its own: a runner key works only from the first host that
uses it. The second runner keeps its host key and its install layout under
~/factory-b/runner/, so it updates itself apart from the first.
The VM was sized for one runner’s builds. Both runners draw on its CPUs,
memory and disk, so raise them as
What the VM needs shows. The Mac’s launch agent starts
the VM, and so both runners, at login. outerlayer doctor on the Mac
reports only the first.
What process hooks give up
Answer no, and the runner usesbuiltin:process. Builds run on the Mac as
the runner’s user, record shared-user, and are not isolated. See
The process fallback.
outerlayer doctor names the fix: outerlayer runner init --vm moves the
runner block into the VM.