> ## Documentation Index
> Fetch the complete documentation index at: https://docs-stg.outerlayer.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up your first host

> Install the runner on one machine, give it its key, give it Claude's credential, and build your first item.

This page takes one Linux machine from nothing to a finished build. On the
way, `outerlayer runner init` installs the service that keeps the runner
running.

Not on Linux, or want the runner image? Find your machine in
[Choose how to run the runner](/run-work-on-your-machines#choose-how-to-run-the-runner).
Then follow the page it names.

## 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 than `npx`.
* **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](#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 init` uses 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](/connect-repository) and
  [GitHub App permissions and build tokens](/github-app-and-build-tokens).
* **Docker Engine 26 or later,** usable by the runner's account.
* **Docker's Buildx plugin, 0.18 or later**, and access to Docker Hub.
  Without it the runner claims nothing. See
  [Image builds](/run-a-host-as-a-service#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 holds `work.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; see
[Credential resolution](/reference/cli#credential-resolution). 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.

```bash theme={"system"}
outerlayer login
```

Then create the key. In the factory, open **Settings**, then the **API keys**
tab (`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.

```bash theme={"system"}
mkdir -p ~/.outerlayer-runner
read -rs RUNNER_KEY && echo "runner key: ${#RUNNER_KEY} characters"   # paste the runner key
read -r FACTORY_ID                                                    # paste the Factory Id
( umask 077; printf '{"url":"https://api.outerlayer.ai","apiKey":"%s","appId":"%s"}\n' "$RUNNER_KEY" "$FACTORY_ID" > ~/.outerlayer-runner/config.json )
```

If the `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.

```bash theme={"system"}
echo "$RUNNER_KEY" | outerlayer login --url https://api.outerlayer.ai --app-id "$FACTORY_ID"
```

## 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:

```bash theme={"system"}
claude setup-token
```

Then give the token to `runner init`. `read -rs` keeps it off the screen
and out of your shell history:

```bash theme={"system"}
read -rs CLAUDE_CODE_OAUTH_TOKEN   # paste the token, then press Enter; nothing is shown
printf '%s' "$CLAUDE_CODE_OAUTH_TOKEN" | outerlayer runner init --agent claude --factory <org>/<factory> --config ~/.outerlayer-runner/config.json
```

`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`](/reference/cli-runner#outerlayer-runner-init).

On a machine that cannot isolate a build, it refuses and writes nothing.
Run it again with `--allow-process-builds` to name `builtin:process`, where
builds run as your account with no isolation. On macOS it offers a Linux VM
instead, which `--vm` accepts; see [Run builds on macOS](/run-builds-on-macos).

<Warning>
  `builtin:process` has no isolation. A build runs as your account, can read
  your files and the runner's own environment, and can reach the network
  directly. See [Security and limits](/build-security) before you use it.
</Warning>

With `--agent claude`, `runner init` also:

* writes a command for each queue that starts Claude Code:
  `claude -p "/build" --permission-mode bypassPermissions` for `implement`,
  and `claude -p "/outerlayer-amend" --permission-mode bypassPermissions`
  for `amend`. `/build` and `/outerlayer-amend` are skills that
  `outerlayer init` installs 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,
  and `runner init` tells the two apart by how they start. It removes any
  Claude credential line from `runner.env`. Any secret a
  [destination](/destinations-and-secrets) reads goes in `runner.env`, and
  `runner init` keeps those lines.

It then installs and starts the service that keeps the runner running,
with the credential loaded; see [Keep it running](#keep-it-running) and
[What the service sets](/run-a-host-as-a-service#what-the-service-sets).

<Warning>
  By default a build can reach every public host, and can send anything it
  can read to any of them. List the hosts your builds need in
  `build.allowHosts`; see
  [Limiting the hosts a build reaches](/container-builds#limiting-the-hosts-a-build-reaches).
</Warning>

To use your own isolation, see [Write your own hooks](/write-your-own-hooks).

### 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

```bash theme={"system"}
outerlayer runner check --config ~/.outerlayer-runner/config.json
```

`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 `claude` command has a `--permission-mode` Claude Code does not accept,
  such as `bypassPermissons`. The message names the command and the value.
* built-in hooks find the credential neither in your shell nor in
  `claude.env` or `runner.env`.
* the key does not hold `work.claim`. Create it with the Runner preset.

If it prints `credentials unverified`, the gateway did not answer
normally. Run it again later.

`outerlayer doctor` then reports what isolation a build gets on this
machine. What each of its runner lines means is in
[`outerlayer doctor`](/reference/cli-capture#outerlayer-doctor).

## 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 `--vm` makes.
* **Windows:** the runner runs from login, as the same service inside
  WSL2.

To set it up again after the config changes, or to remove it, see
[Run the runner as a service](/run-a-host-as-a-service).

### 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:

```bash theme={"system"}
outerlayer runner uninstall --service --config ~/.outerlayer-runner/config.json
outerlayer runner start --config ~/.outerlayer-runner/config.json
```

The runner then needs the credential in that shell's environment. It
refuses to start, naming the problem, when the config is wrong, a hook is
not an executable file, or the key is refused. Its first claim binds the
runner key to this machine; see
[The host key](/run-a-host-as-a-service#the-host-key).

## 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:

```bash theme={"system"}
outerlayer work build --issue <n>
```

The first build of a repository also builds its build image, so it takes longer.
The first microVM build also downloads Firecracker and a guest kernel from
github.com.

## Watch it run

From a second terminal on the host:

```bash theme={"system"}
outerlayer runner status --config ~/.outerlayer-runner/config.json
outerlayer runner logs <item> -f --config ~/.outerlayer-runner/config.json
```

`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](/troubleshoot-builds).
Every key is in [The config file](/reference/config-file), and every
command in [Runner commands](/reference/cli-runner).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.