- Local Self-Hosted Runner using Docker Compose
- Adding a Local Docker Registry
- Add Self-Hosted Apache Maven Registry
The main goal of this post is to create a local docker compose stack, comprising of single container/service, i.e., GitHub self-hosted runner. Some extra effort is involved in preparing the local infrastructure, however, once up and running, adding services to the stack becomes a trivial exercise, mostly involving modifications to the docker-compose.yml
content.

Prerequisites and Content Overview
Examples throughout this article include references to GitHub resources with the following names:
- Username:
my-git-user
name - Git Repository:
- URL:
https://github.com/my-git-username/self-hosted-local-infra-test.git
- Name:
self-hosted-local-infra-test
- URL:
Create the repository above if you intend on following along with the examples presented.
The process was tested on an Ubuntu 20.04
host, with the Docker Engine and Docker Compose installed.
In summary, the points covered are:
- Generate a Classic Personal Access Token (PAT) to allow an entrypoint.sh script to programmatically register a self-hosted runner to the target repository
- Clone an existing repository, containing a sample
docker-compose.yml
, and artifacts required to build the docker image - Build the GitHub Actions Runner agent docker image
- Update the supplied
docker-compose.yml
to reflect the runner configuration - Run the stack and check the status of the runner
- Test the infrastructure by running a test workflow
Generate GitHub PAT
Generate a classic personal access token for with repo scope.

Note down the generated token.
Download Project Files
Download/clone the repository containing sample project files:
cd $HOME
git clone https://github.com/techtoaster-io/ubuntu-self-hosted-gh-runner.git
Build Image
Build the image.
$ cd $HOME/ubuntu-self-hosted-gh-runner
$ docker buildx build --load -t ubuntu-self-hosted-gh-runner:latest \
--build-arg RUNNER_VERSION=$(curl -s -L -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" "https://api.github.com/repos/actions/runner/releases/latest" | jq -r '.tag_name' | cut -c 2-) \
--build-arg RUNNER_USER="runner" \
--build-arg CONTEXT_ROOT_PATH="jammy_stable" \
-f jammy_stable/Dockerfile .
The command creates image ubuntu-self-hosted-gh-runner:latest
.
Update docker-compose.yml
Values of the environment variables in file $HOME/ubuntu-self-hosted-gh-runner/docker-compose.yml
will need to be updated to reflect your environment i.e.,:
# GitHub username
- REPO_OWNER=my-git-username
# Name of the repository that the runner should be registered to
- REPOSITORY=self-hosted-local-infra-test
# Personal Access token
- RUNNER_ADMIN_TOKEN=<classic personal access token>
Variables specific to the self-hosted runner configuration are set the default values below, and can be changed as required.
# JSON array of label names to assign to the runner
- RUNNER_LABELS={"labels":["self-hosted","jammy-amd64"]}
# Name to assign to the runner
- RUNNER_NAME=self-hosted-jammy-amd64
# Any additional arguments to pass to the ./config runner agent command
- RUNNER_CONFIG_ARGS=--unattended --replace --disableupdate --no-default-labels
Run Docker Compose Project
Bring up the stack:
$ cd $HOME/ubuntu-self-hosted-gh-runner
$ docker compose up -d
Monitor the registration status of the self-hosted runner by following the container logs.
$ docker logs -f self-hosted-runner
Sample output:
---
...
NOTICE --- Labels received from env var RUNNER_LABELS (json formatted): self-hosted,jammy-amd64
DEBUG --- Configuring the runner.
--------------------------------------------------------------------------------
| ____ _ _ _ _ _ _ _ _ |
| / ___(_) |_| | | |_ _| |__ / \ ___| |_(_) ___ _ __ ___ |
| | | _| | __| |_| | | | | '_ \ / _ \ / __| __| |/ _ \| '_ \/ __| |
| | |_| | | |_| _ | |_| | |_) | / ___ \ (__| |_| | (_) | | | \__ \ |
| \____|_|\__|_| |_|\__,_|_.__/ /_/ \_\___|\__|_|\___/|_| |_|___/ |
| |
| Self-hosted runner registration |
| |
--------------------------------------------------------------------------------
# Authentication
√ Connected to GitHub
# Runner Registration
√ Runner successfully added
√ Runner connection is good
# Runner settings
√ Settings Saved.
...
DEBUG --- /runner/config.sh --url https://github.com/***********/self-hosted-local-infra-test --token **********
--labels self-hosted,jammy-amd64 --unattended --replace --disableupdate
--no-default-labels --name self-hosted-jammy-amd64
DEBUG --- Runner successfully configured.
...
√ Connected to GitHub
Current runner version: '2.********** '
Listening for Jobs
- The log confirms the runner was registered successfully with the following labels:
- self-hosted
- jammy-amd64
- Check repository Actions settings to ensure the runner appears

Run Test Workflow
Create a sample workflow similar to the following (slightly modified version of github-actions-demo.yml):
name: GitHub Actions Demo for self-hosted runner
run-name: Test out GitHub Actions🚀
on: [push]
jobs:
GitHub-Actions-Test:
runs-on: [self-hosted, jammy-amd64]
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
- run: |
echo "🐧 This job is now running on a self-hosted runner with named: ${{ runner.name }}"
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ github.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
Push the changes.
git add -A
git commit -m "add git-actions-demo.yml"
git push -u origin master
The workflow should execute on our self-host runner.
Check the repository’ workflow logs to ensure all steps executed successfully.
