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

# Install the runner

> Install preloop-runner, register it with GitHub or a Preloop control plane, run it as a service, and remove it.

This page installs `preloop-runner` on a Linux or macOS host, registers it with a GitHub repository, organization, or Preloop control plane, and starts it. You need a registration token and outbound HTTPS access to GitHub. Check [platform support](/runner/overview) first.

## Requirements

* Linux (x86\_64 or arm64) or macOS (Apple Silicon or Intel).
* `git` on the host. The runner uses it to fetch actions and check out code.
* Docker, only for jobs that use `container:`, `services:`, or Docker actions.
* Outbound HTTPS to GitHub, including `api.github.com` and `*.actions.githubusercontent.com`. `configure` also downloads Node 20 and Node 24 from `nodejs.org` unless you pass `--no-externals`.

## Install the binary

**1. Run the installer**

Fetch the installer at a release tag, read it, then run it with `--runner`. That installs only `preloop-runner`, with no control plane or VM tooling.

```sh theme={null}
ver=v0.34.4   # any release tag v0.33.7 or newer; see https://github.com/preloopdev/preloop/releases
curl -fsSL "https://raw.githubusercontent.com/preloopdev/preloop/$ver/install.sh" -o install.sh
less install.sh
sh install.sh --runner
```

The installer checks the binary against the release's SHA-256 file and puts it in `~/.local/bin`. Pass `--prefix <dir>` to install under `<dir>/bin` instead.

**2. Or download the binary**

Each release publishes one binary per platform, with a `.sha256` file and a CycloneDX SBOM.

| Host | Asset |
| - | - |
| Linux x86\_64 | `preloop-runner-x86_64-unknown-linux-gnu` |
| Linux arm64 | `preloop-runner-aarch64-unknown-linux-gnu` |
| macOS Apple Silicon | `preloop-runner-aarch64-apple-darwin` |
| macOS Intel | `preloop-runner-x86_64-apple-darwin` |

```sh theme={null}
triple=x86_64-unknown-linux-gnu   # the asset for your host
base=https://github.com/preloopdev/preloop/releases/latest/download
curl -fsSLO "$base/preloop-runner-$triple"
curl -fsSLO "$base/preloop-runner-$triple.sha256"
if command -v sha256sum >/dev/null 2>&1; then
  actual=$(sha256sum "preloop-runner-$triple" | awk '{print $1}')
else
  actual=$(shasum -a 256 "preloop-runner-$triple" | awk '{print $1}')
fi
test "$actual" = "$(awk '{print $1}' "preloop-runner-$triple.sha256")"
install -m 0755 "preloop-runner-$triple" ~/.local/bin/preloop-runner
```

Check the install with `preloop-runner --version`.

## Get a registration token

Create the token right before you configure the runner. In the repository or organization settings, open **Actions → Runners → New self-hosted runner**, or use the GitHub CLI:

* Repository: `gh api --method POST /repos/OWNER/REPO/actions/runners/registration-token --jq .token`
* Organization: `gh api --method POST /orgs/ORG/actions/runners/registration-token --jq .token`

## Configure and run

**1. Configure the runner**

Run `configure` from the directory the runner will live in. That directory is the runner root. It holds the runner's configuration, `work/` for job workspaces, and `externals/` for Node.

```sh theme={null}
mkdir -p ~/actions-runner && cd ~/actions-runner
preloop-runner configure \
  --url https://github.com/OWNER/REPO \
  --token <REGISTRATION_TOKEN> \
  --name my-runner \
  --labels gpu,fast
```

For an organization runner, use `--url https://github.com/ORG`. `configure` registers the runner, downloads Node 20 and Node 24, and writes `.runner`, `.credentials`, and `.credentials_rsaparams`. Those files are readable only by the owner.

**2. Start the runner**

```sh theme={null}
preloop-runner run
```

The runner connects to GitHub and waits for jobs. Press Ctrl-C to stop it. A running job gets 60 seconds to wind down before the runner stops it and reports it as canceled.

**3. Run it as a service (Linux)**

Create `/etc/systemd/system/preloop-runner.service`. This example expects the binary at `/usr/local/bin/preloop-runner`, so copy it there or change `ExecStart`.

```ini theme={null}
[Unit]
Description=preloop GitHub Actions runner
After=network.target

[Service]
Type=simple
User=runner
WorkingDirectory=/home/runner/actions-runner
ExecStart=/usr/local/bin/preloop-runner run
Restart=always
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
```

```sh theme={null}
sudo systemctl daemon-reload
sudo systemctl enable --now preloop-runner
```

`WorkingDirectory` must be the runner root. Do not combine this unit with `--once` or `--ephemeral`. `Restart=always` starts the runner again after every exit.

## Single-job runners

A runner configured with `--ephemeral` exits after its first job. `run --once` does the same for any configured runner. Use either on hosts that you provision for one job.

```sh theme={null}
preloop-runner configure --url https://github.com/OWNER/REPO --token <REGISTRATION_TOKEN> --ephemeral
preloop-runner run --once
```

## Connect to a Preloop control plane

Point `configure` at the control plane's base URL. Use the control plane's system token as `--token`, not a GitHub registration token. The control plane accepts only that credential for registration.

```sh theme={null}
preloop-runner configure --url https://preloop.example.com --token <SYSTEM_TOKEN> --name my-runner
preloop-runner run
```

> [!NOTE]
>
> The control plane gives the runner the address of its runner service, built from its public URL (`PRELOOP_PUBLIC_URL` on the control plane). Set that URL to an address the runner host can reach. See [control plane configuration](/control-plane/configuration).

## Run in a container

The image `ghcr.io/preloopdev/preloop-runner` supports `linux/amd64` and `linux/arm64`. Its entrypoint is `preloop-runner` and its default command is `run`, so the container does not configure itself. Keep the runner state in a volume, and pass `--runner-root` to each command.

```sh theme={null}
docker run --rm -v preloop-runner:/runner ghcr.io/preloopdev/preloop-runner:latest \
  --runner-root /runner configure --url https://github.com/OWNER/REPO --token <REGISTRATION_TOKEN>

docker run -d --name preloop-runner -v preloop-runner:/runner \
  ghcr.io/preloopdev/preloop-runner:latest --runner-root /runner run
```

The image includes `git`, `curl`, CA certificates, and the Docker CLI with buildx. Container jobs need a Docker daemon that the container can reach.

## Remove the runner

Deregister the runner with a remove token, not a registration token:

```sh theme={null}
gh api --method POST /repos/OWNER/REPO/actions/runners/remove-token --jq .token
preloop-runner remove --token <REMOVE_TOKEN>
```

`remove` deregisters the runner and deletes `.runner`, `.credentials`, and `.credentials_rsaparams`. It leaves `work/` and `externals/` in place, so delete those yourself. If the server cannot deregister the runner, `remove` warns and deletes the local files anyway; remove the runner in the repository or organization runner settings to clear the leftover registration.

## Troubleshooting

| Message | Cause | Fix |
| - | - | - |
| `Runner is already configured in ... Use --replace` | The runner root already contains `.runner`. | Pass `--replace`, or use a different `--runner-root`. |
| `Runner '<name>' already exists` | A runner with that name is already registered. | Pass `--replace`, or choose another `--name`. |
| `loading runner configuration ... run preloop-runner configure first` | `run` found no configuration in the runner root. | Run from the runner root, or pass `--runner-root`. |
| `bundled node20 is missing` | You configured with `--no-externals`, and the `node` on `PATH` does not match the action's Node version. | Install the matching Node version, or run `configure` without `--no-externals`. |


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