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

# Update a runner

> The commands that move a runner to a new CLI version, for each way a runner runs, and how to check that the update worked.

A service runner and a runner in a Lima VM update themselves. Update any
other runner by hand when one of these says that it needs an update:

* the runner's log;
* the factory's **Settings → Runners** page;
* an email from OuterLayer;
* a Work page item that reads "*runner* needs an update".

OuterLayer refuses work from a runner outside the
[support window](https://docs.outerlayer.ai/run-a-host-as-a-service.md#the-support-window). That section
gives the dates and the emails.

## Run the update

How a runner updates depends on where the runner itself runs, not on where
its builds run. Find how you started the runner, then run the commands for
it.

| You started the runner with | Where the runner runs | Updates itself |
| - | - | - |
| `docker run … ghcr.io/outerlayerai/runner` | [In the runner image](#the-runner-image), on Docker Engine or Docker Desktop | No |
| `outerlayer runner install --service` or `outerlayer runner init` on Linux or in WSL2 | [As a service on the machine](#a-service-runner) | Yes |
| `outerlayer runner init --vm` on a Mac | [As a service in a Lima VM on the Mac](#a-runner-in-a-lima-vm-on-a-mac) | Yes |
| `outerlayer runner start` in a terminal | [In that terminal](#a-runner-started-in-a-terminal) | No |

The commands install the newest release. If the factory's **Runner updates**
setting is **Hold at a version**, put that version in place of `latest`.

The `outerlayer runner` commands below read the default config file,
`~/.outerlayer/config.json`. If you started the runner with `--config <path>`,
as [Set up your first host](https://docs.outerlayer.ai/set-up-a-host.md) does, add the same `--config <path>`
to each of them. A runner in the image or in a Lima VM needs none.

### The runner image

The runner image never updates itself. Replace the container with a new one
that has the same flags and the same mounts. Builds survive the change.

> [!WARNING]
>
> Do not remove the old container first. Docker forgets a container's mounts
> when it removes the container. The runner keeps its config file and host key
> in the mount at `/root/.outerlayer`.

1. Print the commands for your container. If your container has another
   name, use that name in place of `outerlayer-runner`. `docker ps` lists
   the names.

   ```bash theme={"system"}
   docker exec outerlayer-runner outerlayer runner update-steps
   ```

   The command prints the pull, stop, rename, run, check and remove steps,
   with your container's name, mounts, restart policy and network. It names
   the variables the container sets and never prints a value. For a
   container that Docker Compose started, it prints `docker compose` steps.

2. Run the printed steps in order. The steps rename the old container to its
   name with `-old` added. They remove it only after the check that the new
   runner is up.

The command is in version 0.34.0 and later. An older container answers
`error: unknown command 'update-steps'`. Then
[write the commands yourself](#write-the-commands-yourself).

#### Go back to the old container

If the new runner does not start, or its log says that OuterLayer refuses
work from it, go back to the old container. This works only before you
remove the old container:

```bash theme={"system"}
docker rm -f outerlayer-runner
docker rename outerlayer-runner-old outerlayer-runner
docker start outerlayer-runner
```

#### If you removed the old container

The runner's state is still on disk, in the volume or folder that holds
`config.json` and `runner/host-key.pem`. Mount it at `/root/.outerlayer` in
the new container.

If you cannot find that volume or folder, mount an empty one. The factory
then refuses the runner key, because the key is bound to the old host key.
Do one of these:

* In **Settings → API keys**, select **Clear host key** on the runner key's
  row.
* Create a new runner key and put it in your env file.

The runner's next claim binds its new host key.

#### Write the commands yourself

Use these commands only if you cannot print the steps. You cannot print them
in two cases:

* The container runs a version before 0.34.0.
* The runner cannot read its container. The command then prints the pull
  step and says why.

Put your own values in place of the placeholders:

* `<name>`: the container's name. `docker ps` lists it.
* `<state>`: the volume or folder mounted at `/root/.outerlayer`. The first
  command prints one line for each mount. `<state>` is the part before
  `:/root/.outerlayer` on that line.
* `<version>`: the version that the runner's log names, such as `0.34.0`.
* `<env-file>`: the env file you started the container with.

Give the new container each other mount and flag that the old one has.

```bash theme={"system"}
docker inspect <name> --format '{{range .Mounts}}{{if .Name}}{{.Name}}{{else}}{{.Source}}{{end}}:{{.Destination}}{{"\n"}}{{end}}'
docker pull ghcr.io/outerlayerai/runner:<version>
docker stop <name>
docker rename <name> <name>-old
docker run -d --name <name> --restart unless-stopped \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v "<state>:/root/.outerlayer" \
  --env-file <env-file> \
  ghcr.io/outerlayerai/runner:<version>
docker logs <name>
docker rm <name>-old
```

The image holds the CLI, so there is nothing to install with npm for the
runner. The `outerlayer` command on the machine itself is a separate copy.
Update it with `npm install -g @outerlayer/cli@latest`.

### A service runner

A service runner updates itself unless `runner.autoUpdate` is `false`. To
update it by hand:

```bash theme={"system"}
npm install -g @outerlayer/cli@latest
"$(npm prefix -g)/bin/outerlayer" runner install --service --config ~/.outerlayer-runner/config.json
```

Use the full path in the second command. The `outerlayer` command in
`~/.local/bin` still runs the old version. Leave out `--config` if you
started the runner without it.

Do not restart the service. It restarts on the new version when its running
builds allow it. See [Upgrade by hand](https://docs.outerlayer.ai/run-a-host-as-a-service.md#upgrade-by-hand).

For a runner with hooks of your own, run the second command without
`--service`. It prints the line that starts the runner on the new version.
Restart your own service to apply it.

### A runner in a Lima VM on a Mac

The runner in the VM updates itself. Installing the CLI on the Mac does not
update it. To update it by hand:

1. Open a shell in the VM:

   ```bash theme={"system"}
   limactl shell outerlayer-runner
   ```

2. Run these commands in that shell:

   ```bash theme={"system"}
   sudo npm install --global @outerlayer/cli@latest
   "$(npm prefix -g)/bin/outerlayer" runner install --service
   ```

### A runner started in a terminal

A runner started from a global npm install does not update itself.

```bash theme={"system"}
outerlayer runner stop --config ~/.outerlayer-runner/config.json
npm install -g @outerlayer/cli@latest
outerlayer runner start --config ~/.outerlayer-runner/config.json
```

On Linux, run `outerlayer runner install --service --config ~/.outerlayer-runner/config.json`
in place of `outerlayer runner start`. The runner then runs as a service and
updates itself.

## Check that it worked

1. Run `outerlayer --version` where the runner runs. For the runner image,
   run `docker exec outerlayer-runner outerlayer --version`. It prints the
   new version.
2. Run `outerlayer runner check` in the same place, with the `--config` you
   started the runner with. It reports no failure.
3. Open the factory's **Settings → Runners** page. It shows the runner as
   **Ready**, at the new version.

The runner takes work again on its next poll. Items that waited while the
runner was behind start by themselves, because a refused claim uses up no
build request.

If the runner's log says that the microVM helper needs reinstalling, see
[Update the microVM helper](https://docs.outerlayer.ai/run-a-host-as-a-service.md#update-the-microvm-helper).

## Stop or limit updates

To stop or limit updates for every runner of a factory, see
[Pausing or holding updates for a factory](https://docs.outerlayer.ai/run-a-host-as-a-service.md#pausing-or-holding-updates-for-a-factory).


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