Pre-built, production-ready GitHub Actions self-hosted runners in Docker containers. Fork this repo to customize runners for your team's tech stack.
| Runner | Description | Image |
|---|---|---|
| base-runner | Foundation image with Node.js, .NET, gh CLI | ghcr.io/ainova-systems/docker-runners/base-runner |
| ai-runner | AI automation with Claude CLI & Cursor CLI | ghcr.io/ainova-systems/docker-runners/ai-runner |
| build-runner | Builds & tests without Docker socket | ghcr.io/ainova-systems/docker-runners/build-runner |
| docker-runner | CI/CD with Docker CLI for container builds | ghcr.io/ainova-systems/docker-runners/docker-runner |
| test-runner | E2E testing with Playwright & Chromium | ghcr.io/ainova-systems/docker-runners/test-runner |
docker run -d --name ai-runner --restart unless-stopped \
-e GITHUB_REPOSITORY_URL=https://github.com/your-org/your-repo \
-e GITHUB_RUNNER_TOKEN=YOUR_TOKEN \
-v ai-runner-data:/home/runner/actions-runner \
ghcr.io/ainova-systems/docker-runners/ai-runner:latestπ‘ API Keys: Add
ANTHROPIC_API_KEY(orCLAUDE_CODE_OAUTH_TOKEN) andCURSOR_API_KEYto GitHub repository secrets. See AI Runner docs.
docker run -d --name docker-runner --restart unless-stopped \
-e GITHUB_REPOSITORY_URL=https://github.com/your-org/your-repo \
-e GITHUB_RUNNER_TOKEN=YOUR_TOKEN \
-v docker-runner-data:/home/runner/actions-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
ghcr.io/ainova-systems/docker-runners/docker-runner:latestdocker run -d --name build-runner --restart unless-stopped \
-e GITHUB_REPOSITORY_URL=https://github.com/your-org/your-repo \
-e GITHUB_RUNNER_TOKEN=YOUR_TOKEN \
-v build-runner-data:/home/runner/actions-runner \
ghcr.io/ainova-systems/docker-runners/build-runner:latestπ‘ build-runner vs docker-runner: Use build-runner for simple build/test jobs (no Docker socket = more secure). Use docker-runner only when you need to build Docker images.
- Go to your repository β Settings β Actions β Runners
- Click New self-hosted runner
- Copy the token from the configure command
β οΈ Tokens expire in 1 hour. Generate a new one if setup fails.
This repo is designed to be forked. Customize runners for your team:
# Fork on GitHub, then clone
git clone https://github.com/YOUR-ORG/docker-runners.git
cd docker-runnersEdit base-runner/Dockerfile to add your team's dependencies:
# Add Python
RUN apt-get update && apt-get install -y python3 python3-pip
# Add Go
ARG GO_VERSION=1.21
RUN wget https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz \
&& tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz \
&& rm go${GO_VERSION}.linux-amd64.tar.gz
ENV PATH="${PATH}:/usr/local/go/bin"
# Add your global npm packages
RUN npm install -g pnpm turboCreate a new runner extending base:
# my-runner/Dockerfile
ARG BASE_IMAGE=ghcr.io/YOUR-ORG/docker-runners/base-runner:latest
FROM ${BASE_IMAGE}
USER root
RUN apt-get update && apt-get install -y your-tools
USER runner
ENV RUNNER_LABELS=self-hosted,my-runnerIn .github/workflows/build-runners.yml, change:
env:
IMAGE_PREFIX: ghcr.io/YOUR-ORG/docker-runnersgit add -A
git commit -m "Customized runners for our team"
git push origin main
# GitHub Actions will build and push images to your GHCRβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β base-runner β
β Ubuntu 24.04 β Node.js 24 β .NET 10 β gh CLI β Actions Runner β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β² β² β²
β β β
ββββββ΄βββββ βββββββ΄ββββββ ββββββ΄ββββββ
βai-runnerβ βbuild-runnerβ βdocker-runnerβ βtest-runnerβ
βClaude β βLabels only β βDocker CLI β βPlaywright β
βCursor β βNo socket β βCompose β βChromium β
βββββββββββ βββββββββββββ βββββββββββββ βββββββββββββ
We use a single full-stack base image pattern (similar to GitHub's official runners):
- β Simple to fork β customize one Dockerfile, not a complex hierarchy
- β Fast child builds β ai-runner and docker-runner only add their specific tools
- β Consistent environment β all runners share the same Node.js, .NET versions
- β Easy maintenance β update dependencies in one place
Trade-off: Base image includes Node.js + .NET even if you only need one. For most full-stack teams, this is acceptable. If you need minimal images, fork and remove unused tools from base-runner.
| Component | Version | Purpose |
|---|---|---|
| Ubuntu | 24.04 LTS | Base OS |
| Node.js | 24.x | JavaScript runtime |
| .NET SDK | 10.0 | C# builds |
| GitHub CLI | latest | PR operations |
| dotnet-ef | latest | Database migrations |
| js-yaml | latest | YAML validation |
| ripgrep | latest | Fast code search |
| GitHub Runner | 2.331.0 | Actions execution |
Override versions when building:
docker build \
--build-arg RUNNER_VERSION=2.320.0 \
--build-arg NODE_MAJOR=22 \
--build-arg DOTNET_VERSION=9.0 \
-t my-base-runner:latest \
base-runner/docker-runners/
βββ .github/workflows/
β βββ build-runners.yml # CI pipeline: base β ai β build β docker
βββ base-runner/ # π§ Customize this!
β βββ Dockerfile # Core dependencies
β βββ entrypoint.sh # Startup script
βββ ai-runner/ # AI automation
β βββ Dockerfile # + Claude, Cursor CLI
β βββ entrypoint.sh # + API key handling
β βββ docker-compose.yml # Local development
β βββ .env.example
βββ build-runner/ # Builds & tests (no Docker socket)
β βββ Dockerfile # Labels only, no extra deps
β βββ docker-compose.yml # Local development
β βββ .env.example
βββ docker-runner/ # Docker container builds
β βββ Dockerfile # + Docker CLI, Compose
β βββ entrypoint.sh
β βββ docker-compose.yml # With Docker socket mount
β βββ .env.example
βββ test-runner/ # E2E browser testing
β βββ Dockerfile # + Playwright, Chromium
β βββ entrypoint.sh
β βββ docker-compose.yml # Local development
β βββ .env.example
βββ README.md
The CI pipeline builds images in dependency order:
- base-runner changes β Rebuild base + all child images
- ai-runner changes β Rebuild only ai-runner
- build-runner changes β Rebuild only build-runner
- docker-runner changes β Rebuild only docker-runner
- test-runner changes β Rebuild only test-runner
- Manual trigger β Force rebuild all
# View logs
docker logs <runner-name> -f
# Stop runner
docker stop <runner-name>
# Restart runner
docker restart <runner-name>
# Remove container (keeps data volume)
docker rm -f <runner-name>
# Full reset (removes credentials, requires new token)
docker rm -f <runner-name> && docker volume rm <runner-name>-data- Fork the repository
- Create your feature branch (
git checkout -b feature/add-python) - Commit changes (
git commit -m "Added Python 3.12 to base runner") - Push to branch (
git push origin feature/add-python) - Open a Pull Request
MIT License - see LICENSE for details.
Keywords: GitHub Actions, self-hosted runner, Docker, CI/CD, DevOps, automation, Claude CLI, Cursor CLI, Node.js, .NET