A self-hosted runner is the open-source program behind GitHub 29 's own machines, actions/runner (https://github.com/actions/runner 6,293 ) (2.337.0 here), installed where you choose: a GPU server, a machine that can reach a staging database, an ARM board. It long-polls GitHub over outbound HTTPS, so it needs no open port, and registers with a one-hour token from the REST API. Settings > Actions > Runners prints a download, config.sh and run.sh sequence; BookNest runs the same program in a throwaway container:
RUNNER_TOKEN=$(gh api -X POST --jq .token \
repos/{owner}/{repo}/actions/runners/registration-token)
docker run -d --name l1-runner --entrypoint sleep \
ghcr.io/actions/actions-runner:2.337.0 infinity
docker exec l1-runner ./config.sh --unattended --ephemeral --disableupdate \
--url https://github.com/binarybehemoth/booknest --token "$RUNNER_TOKEN" \
--name l1-booknest-runner --labels l1-booknest-demo
unset RUNNER_TOKEN564cfe871302b024e03ca6e98c9db52ac0a8b60615cb4df751301568331d2d36 ... √ Runner successfully added ...
--ephemeral makes the runner take one job and then unregister; --disableupdate stops it replacing its own binary. Pull request #25 added self-hosted.yml, a workflow_dispatch workflow whose job prints where it ran. The owner dispatches it, and the container, with every file the runner wrote, is then deleted:
docker exec l1-runner ./run.sh > run.log 2>&1 &
gh workflow run self-hosted.yml && sleep 45
grep -v '^\[' run.log
docker rm -f l1-runner
gh api repos/{owner}/{repo}/actions/runners --jq .total_counthttps://github.com/binarybehemoth/booknest/actions/runs/36147682753 ... 2026-09-25 14:28:15Z: Listening for Jobs 2026-09-25 14:28:19Z: Running job: where 2026-09-25 14:28:35Z: Job where completed with result: Succeeded √ Removed .credentials √ Removed .runner ... l1-runner 0
The job's log printed user: runner on 564cfe871302: an unprivileged user inside the container. A persistent runner runs as a service (sudo ./svc.sh install) and is removed with ./config.sh remove --token <token>, using a token from the remove-token endpoint.