outerlayer runner init installs the service that keeps the runner
running.
Before you begin
You need:- Node.js 22 or later, and the CLI:
npm install -g @outerlayer/cli. A service needs a fixed path to start, so use the global install rather thannpx. - A way to create an API key on the factory. The owner, admin and write roles can. If you cannot, a member who can creates the runner key and gives it to you; see If you cannot create the key.
- The factory’s name, as
<org>/<factory>. It is in the factory’s dashboard address,app.outerlayer.ai/orgs/<org>/factories/<factory>. If your login can use only one factory,runner inituses it and you need no name. - The GitHub App installed on the repository. Otherwise every build
fails at provision with
repository_not_in_installation. See Connect a repository and GitHub App permissions and build tokens. - Docker Engine 26 or later, on Linux or WSL2, usable by the runner’s account. Docker Desktop alone is not enough for a runner installed on the machine. On a Mac, follow Run builds on macOS. On Windows, follow Run builds on Windows. To run the runner from a container image with no CLI installed, follow Run the runner in a container.
- On a Mac, Docker Desktop and the runner image. The image runs the runner on the Docker Desktop that the Mac already has, with no CLI to install. Follow Run the runner in a container.
- On a Mac without Docker, Lima.
outerlayer runner init --vmuses it to run the Linux VM that the runner lives in. Install it withbrew install lima; see Lima’s install page. - Docker’s Buildx plugin, 0.18 or later, and access to Docker Hub. The
runner builds each repository’s image in a builder of its own. It is
docker-buildx-pluginin Docker’s apt repository. Without it the runner claims nothing. See Image builds. - For microVM builds: x86_64 Linux where the runner’s account can open
/dev/kvm. Without it, builds run in containers.
Give the runner its key
The runner needs a key of its own: an API key with the Runner preset. The preset holdswork.claim and hosts.ingest, among others. A login never creates a key, so
runner init asks you for one. When the runner’s config file has no
apiKey, runner init prints the link to the factory’s API keys page and
asks you to paste the key. Nothing is shown as you paste; press Enter when
you are done. It checks that the gateway accepts the key, and writes it into
the config file. Each runner gets its own key, because a runner key works only
with the first host key that signs for it.
A factory key outranks an account login. If runner init wrote the key
into ~/.outerlayer/config.json, it would replace your own login there, and
your sessions would switch factory. Give the runner a config file of its own
instead: the commands below pass --config ~/.outerlayer-runner/config.json
to each outerlayer runner command. runner init creates that file, owner
only, and keeps its host key, install layout, claude.env and runner.env beside it.
Log in on the machine first, and approve the link it prints. The login
tells runner init which factory you can use. On a machine with no browser,
add --no-browser and open the link on another machine.
app.outerlayer.ai/orgs/<org>/factories/<factory>/settings/api-keys),
choose Create API key, and pick the Runner preset. The key is shown
once, so keep the page open until runner init has accepted it.
If you cannot create the key
Ask a member who can create keys to make one with the Runner preset.runner init also stops, and prints the same link, when it has no terminal
to ask on. That happens in a script, or in a session over a connection with no
terminal.
In either case, put the key in the config file yourself. Run these lines one
at a time. Each read waits for you to paste a value and press Enter.
read -rs shows nothing as you paste, so the key stays off the screen. The
echo after it prints only the key’s length, so you can tell the paste
worked. The Factory Id is on the factory’s Settings → General tab.
echo prints 0 characters, the key is empty. Run the read line
again before you write the file. runner init below then uses this key and
asks for none. Leave --factory out of the commands below, because a config
file that holds a key already names its factory.
On a machine that only runs the runner, you can save the key with
outerlayer login instead. The key is read from stdin, so it stays out of
your shell history. The dialog that shows the new key also shows this
command, filled in. If you save it this way, leave --config out of every
command below.
Set it up
The runner needs a Claude credential. A build never sees the credential itself. Create a token; on a host with no browser,claude setup-token
prints a link to open on another machine:
runner init. read -rs keeps it off the screen
and out of your shell history:
- Linux
- macOS
runner init writes the runner’s block of the config file: ~/.outerlayer/config.json,
or the file you gave with --config. When that file has no apiKey, it
first asks you to paste the runner key for the factory --factory
names. It checks the key before it changes anything else. With no --factory,
it uses the only factory your login can use and says which. If your login
can use several, it lists each as a --factory value and stops. --app-id
takes the Factory Id from the factory’s Settings → General tab
instead. It names the built-in hooks that fit the machine: builtin:vm on x86_64 Linux
whose account can open /dev/kvm, else builtin:container where Docker
Engine 26 or later runs. The full rule is in
outerlayer runner init.
- On macOS,
--vmruns the runner in a Linux VM and--no-vmdeclines it. With neither, a terminal gets the question, and a piped command such as the one above refuses and names the runner image and--vm. See Run builds on macOS. - On any other machine that cannot isolate a build, it refuses and writes
nothing. Run it again with
--allow-process-buildsto namebuiltin:process, where builds run as your account with no isolation.
--agent claude, runner init also:
- writes a command for each queue that starts Claude Code:
claude -p "/build" --permission-mode bypassPermissionsforimplement, andclaude -p "/outerlayer-amend" --permission-mode bypassPermissionsforamend./buildand/outerlayer-amendare skills thatouterlayer initinstalls in the repository. - stores the token in
claude.env, beside the config file, readable only by your account. An Anthropic API key works too: pipe it the same way, andrunner inittells the two apart by how they start. It removes any Claude credential line fromrunner.env. Any secret a destination reads goes inrunner.env, andrunner initkeeps those lines.
Using another agent
Without--agent, runner init writes a placeholder command for each
queue, and stores no credential. The runner claims nothing while a queue’s
command is still the placeholder. Edit runner.commands in the config file
to start your agent on item $OUTERLAYER_WORK. The running service reads
the edited file on its next poll, with no restart.
Put the credentials your agent needs in runner.env, readable only by the
runner’s account, and restart the service, which reads that file only when
it starts. Claude’s credential is the exception: it lives in claude.env.
Check the host before the runner takes work
check validates the config, confirms the key works, and prints the
settings the runner would use. It takes the Claude credential from your
shell, and from the service’s credentials directory. When the shell has
none, it reads the claude.env beside the config, then runner.env. It
names the file when it used it. It exits 1 and says what to fix when:
- a queue’s command is still the placeholder.
- a
claudecommand has a--permission-modeClaude Code does not accept, such asbypassPermissons. The message names the command and the value. - built-in hooks find the credential neither in your shell nor in
claude.envorrunner.env. - the key does not hold
work.claim. Create it with the Runner preset.
credentials unverified, the gateway did not answer
normally. Run it again later.
outerlayer doctor reports what isolation a build gets on this machine. It
reads the credential from the service’s claude.env, then runner.env.
Keep it running
outerlayer runner init already set up what keeps the runner running, with
the same command on every system. You write no service file. It restarts
the runner after a crash, a reboot and a self-update. It asks for sudo
where it needs root, and names each command first.
- Linux: the runner runs from boot, as a systemd service.
- macOS: the runner runs from login, as the same service inside the
Linux VM that
--vmmakes. - Windows: the runner runs from login, as the same service inside WSL2.
Run the runner in a terminal instead
The service and a terminal runner cannot run on the same config. To run the runner in a terminal, remove the service first:Build your first item
Ask for a build as yourself, not with the runner key. A build requested with a runner key gets no repository tokens, so it fails. Either:- choose Build beside the issue on the Work page, or
- run this from your own machine:
Watch it run
From a second terminal on the host:status lists running builds and their stage. logs -f follows one
item’s build log. outerlayer runner status --recent shows finished
builds. The session and the pull request land on the item’s page.
If something goes wrong, see Troubleshoot builds.
Every key is in The config file, and every
command in Runner commands.