Self-hosted runner
The runner container runs the same harness as the GitHub Action, for runners outside GitHub and for networks with no inbound access. It claims jobs from Loopback over HTTPS: no inbound port, no webhook.
Create a runner token
In Settings, create a runner token for one workspace. It starts with lbr_, is shown once, expires, and only its hash is stored.
Run it
# Once: Docker's default seccomp profile, plus the user namespaces bubblewrap needs (Isolation)docker run --rm ghcr.io/loopback-dev/runner:1 seccomp-profile > loopback-seccomp.json docker run -d --name loopback-runner --restart unless-stopped \ --security-opt seccomp=loopback-seccomp.json \ -e LOOPBACK_API_URL=https://api.loopback.so \ -e LOOPBACK_RUNNER_TOKEN=lbr_... \ -e LOOPBACK_AGENT_KEY=sk-ant-... \ -v loopback-cache:/cache \ ghcr.io/loopback-dev/runner:1 pollThe first command saves the container’s seccomp profile (see Isolation). poll claims one job at a time and finishes the current job when the container stops. Give it time to finish.
docker stop -t 1800 loopback-runner # the current job finishes firstEnvironment
| Variable | Description |
|---|---|
LOOPBACK_API_URL | Loopback API address. |
LOOPBACK_RUNNER_TOKEN | The runner token (required for poll). |
LOOPBACK_AGENT_KEY | The key of the agent’s model provider. |
LOOPBACK_AGENTS | Agents this runner accepts, comma-separated, for example builtin,claude-code. |
LOOPBACK_CONCURRENCY | Jobs run at the same time (default 1). |
LOOPBACK_RUNNER_NAME | A name shown in the dashboard. |
LOOPBACK_WORKDIR | Directory for clones and temporary state. |
LOOPBACK_CACHE_DIR | Persistent cache of package managers and tools. |
LOOPBACK_ALLOW_INSTALL | 1 installs missing agent CLIs and the engine with npm into the cache. |
VEXP_LICENSE_TOKEN | The engine licence used when a job brings none, as on a self-hosted install of Loopback: the licence of your contract. A job’s own licence wins. |
Network
- Outbound HTTPS only: to the Loopback API, to GitHub, to your model provider and to your package registries.
- Each claimed job comes with a job token for that job and a separate token for the agent’s MCP tools.
- Secrets go in the environment, never in command-line flags, which every process can read.
Isolation
The container runs as a non-root user and starts the agent in its own process group. The repository’s installs, tests and type-check run only inside bubblewrap, away from the runner’s tokens. bubblewrap needs user namespaces, which Docker’s default seccomp profile blocks.
- Run the container with the profile its image prints,
seccomp-profile: Docker’s default, plusclone,unshare,setns,mount,umount2andpivot_rootfor unprivileged processes, on a host that allows unprivileged user namespaces (on Ubuntu 24.04,kernel.apparmor_restrict_unprivileged_userns=0). gVisor or Kata work too, and are the way where AppArmor still blocks bubblewrap. - Without them the runner says so in its log at startup. Fixes are then reported with their tests not run, and with
VEXP_MODE=sdkno fix is proposed, because its verification needs the tests and the type-check. - Codex runs inside bubblewrap too, in its own PID namespace, with the SSH keys, the cloud and registry logins and the Actions runner registration of the machine hidden. It isolates its own commands with user namespaces as well, read-only outside a fix. Without them the runner does not start Codex.
LOOPBACK_CODEX_SANDBOX=danger-full-accessis refused: the runner’s tokens are in the same container, so the container cannot be the isolation boundary.