The official ways to scale self-hosted CI runners assume Kubernetes: GitHub’s Actions Runner Controller, and Atlassian’s runners-autoscaler for Bitbucket Pipelines. I wanted runners I could run and scale on a plain Docker host, so I built one image per platform: github-actions-runner and bitbucket-pipeline-runner.
They do not autoscale. Scaling is one docker service scale away, but nothing watches the job queue and does it for you. They are not ephemeral either: a container keeps taking jobs until it is stopped.
The approach follows testdriven.io’s guide to running GitHub Actions in Docker, credited in both READMEs.
How does a runner register and clean up after itself?
Through the platform’s API, from the container’s entrypoint. On start, the container asks for a registration and registers a new runner. On SIGINT or SIGTERM it removes that runner again. The GitHub image’s entrypoint, trimmed:
REG_TOKEN=$(curl -sX POST -H "Authorization: token ${ACCESS_TOKEN}" \
"https://api.github.com/orgs/${ORGANIZATION}/actions/runners/registration-token" | jq .token --raw-output)
./config.sh --url "https://github.com/${ORGANIZATION}" --token "${REG_TOKEN}"
trap 'cleanup; exit 130' INT
trap 'cleanup; exit 143' TERM
The Bitbucket image wraps Atlassian’s official runner image and registers through the workspace runners API. It accepts either a workspace token or a user and API token, with the read:runner:bitbucket, write:runner:bitbucket and read:user:bitbucket scopes.
How do I run them on Docker Swarm?
Set the organisation or workspace and a token, then scale the service. The same images run under Docker Compose, Swarm or a Kubernetes Deployment. On Swarm:
services:
github-runner:
image: samjuk/github-actions-runner:latest
environment:
ORGANIZATION: acme
ACCESS_TOKEN: ghp_xxxx
bitbucket-runner:
image: samjuk/bitbucket-pipelines-runner:latest
environment:
WORKSPACE: acme
AUTH_TOKEN: xxxx
docker service scale ci-runners_bitbucket-runner=3
How do new runner versions get published?
Without me. A watcher runs daily at 05:30 UTC, compares the upstream releases (GitHub’s actions/runner releases, Atlassian’s runner changelog) with the tags already published and builds any version that is missing. The Bitbucket watcher skips versions known to be broken upstream, such as those with a missing base image.
Each release runs the bats tests, pushes latest and the version tag to Docker Hub and GHCR, and cuts a GitHub release. Third-party actions are pinned by commit SHA. Images are built for linux/amd64 only.
| GitHub Actions | Bitbucket Pipelines | |
|---|---|---|
| Base | Ubuntu 22.04 with actions/runner | Atlassian’s official runner image |
| Registers with | Organisation registration token from a PAT | Workspace runners API |
| Versions published | 135, latest 2.338.0 (7th October 2026) | 41, latest 6.0.8 (29th September 2026) |