Hosting your self hosted runners on GitHub Codespaces



Welcome to another part of my series ‘GitHub Codespaces Pro Tips’. In the last part we spoke about integrating GitHub with Azure DevOps and how you can use some of the great features of GitHub, like Codespaces along with Azure DevOps.

In todays post I will share with you how you can use your GitHub Codespace not only as a “development environment” for working with your code, but also utilising the Codespace compute power, by running a Self Hosted GitHub runner inside of the Codespace at the same time.

We will be using a custom docker image that will automatically provision a self hosted runner agent and register it at the same time as provisioning the Codespace as part of the development environment.


We will also look at the Codespace/Runner lifecycle. By default any Active codespaces that becomes idle will go into a hibernation mode after 30 minutes to save on compute costs, so we will look at how this timeout can be configured and extended (if needed).

We will actually be using a very similar approach for the docker image configuration based on one of my previous blog posts, ‘Create a Docker based Self Hosted GitHub runner Linux container’. So do check out that post also if you wanted more info on how self hosted GitHub runner containers work.

Getting started

All of the code samples and examples are also available on my GitHub Codespaces Demo Repository.

Since Codespaces/Dev containers are based on docker images, we will create a custom linux docker image that will start and bootstrap a runner agent as the codespace starts up.

We will create the following folder structure tree in the root of our GitHub repository:


In your GitHub repository create a sub folder under '.devcontainer', in my case I have called my codespace configuration folder 'codespaceRunner'.

Next, create the following Dockerfile:

# You can pick any Debian/Ubuntu-based image. 😊

# [Optional] Install zsh
# [Optional] Upgrade OS packages to their latest versions

# Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies.
COPY library-scripts/*.sh /tmp/library-scripts/
RUN bash /tmp/library-scripts/ "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" "true" "true"

# cd into the user directory, download and unzip the github actions runner
RUN cd /home/vscode && mkdir actions-runner && cd actions-runner

#input GitHub runner version argument

RUN cd /home/vscode/actions-runner 
    && curl -O -L${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz 
    && tar xzf /home/vscode/actions-runner/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz 
    && /home/vscode/actions-runner/bin/

# copy over the script
COPY library-scripts/ /home/vscode/actions-runner/

# Apply ownership of home folder
RUN chown -R vscode ~vscode

# make the script executable
RUN chmod +x /home/vscode/actions-runner/

# Clean up
RUN rm -rf /var/lib/apt/lists/* /tmp/library-scripts

Then create a 'devcontainer.json' file. (See my previous blog post on how this file can be amended with additional features and extensions):

    "name": "CodespaceRunner",
    "dockerFile": "Dockerfile",

    // Configure tool-specific properties.
    "customizations": {
        // Configure properties specific to VS Code.
        "vscode": {
            // Add the IDs of extensions you want installed when the container is created.
            "extensions": [

    // Use 'forwardPorts' to make a list of ports inside the container available locally.
    // "forwardPorts": [],

    // Use 'postStartCommand' to run commands each time the container is successfully started..
    "postStartCommand": "",

    // Comment out to connect as root instead. More info:
    "remoteUser": "vscode",
    "build": {
        "args": {
            "UPGRADE_PACKAGES": "true",
            "RUNNER_VERSION": "2.295.0"
    "features": {
        "terraform": "latest",
        "azure-cli": "latest",
        "git-lfs": "latest",
        "github-cli": "latest",
        "powershell": "latest"

NOTE: You can amend the GitHub runner version by amending the build args attribute RUNNER_VERSION.

"build": {
    "args": {
        "UPGRADE_PACKAGES": "true",
        "RUNNER_VERSION": "2.295.0"

Next we will create a folder with a few scripts that will be used by our docker image.

Create a folder called 'library-scripts' and place the following two script inside: ‘’ and ‘’

Let’s take a closer look at each of the scripts.

  1. This script will install additional debian based tooling onto the dev container.



USER_NAME_LABEL=$(git config --get

REG_TOKEN=$(curl -sX POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${GH_TOKEN}"${GH_OWNER}/${GH_REPOSITORY}/actions/runners/registration-token | jq .token --raw-output)

/home/vscode/actions-runner/ --unattended --url${GH_OWNER}/${GH_REPOSITORY} --token ${REG_TOKEN} --name ${RUNNER_NAME}  --labels ${USER_NAME_LABEL},${REPO_NAME_LABEL}

cleanup() {
    echo "Removing runner..."
    /home/vscode/actions-runner/ remove --unattended --token ${REG_TOKEN}

trap 'cleanup; exit 130' INT
trap 'cleanup; exit 143' TERM
trap 'cleanup' SIGINT SIGTERM

/home/vscode/actions-runner/ & wait $!

The second script will start up with the Codespace/Dev container and bootstraps the GitHub runner when the Codespace starts. Notice that we need to provide the script with some parameters:


These parameters (environment variables) are used to configure and register the self hosted github runner against the correct repository.

We need to provide the GitHub account/org name via the 'GH_OWNER' environment variable, repository name via GH_REPOSITORY and a PAT token with GH_TOKEN.

You can store sensitive information, such as tokens, that you want to access in your codespaces via environment variables. Let’s configure these parameters as encrypted secrets for codespaces.

  1. Navigate to the repository 'Settings' page and select 'Secrets -> Codespaces', click on 'New repository secret'. image.png

  2. Create each Codespace secret with the values for your environment. image.png

NOTE: When the self hosted runner is started up and registered, it will also be labeled with the ‘user name’ and ‘repository name’, from the following lines. (These labels can be amended if necessary):

USER_NAME_LABEL=$(git config --get

Note on Personal Access Token (PAT)

See creating a personal access token on how to create a GitHub PAT token. PAT tokens are only displayed once and are sensitive, so ensure they are kept safe.

The minimum permission scopes required on the PAT token to register a self hosted runner are: "repo", "read:org":


Tip: I recommend only using short lived PAT tokens and generating new tokens whenever new agent runner registrations are required.

Deploying the Codespace GitHub runner

As you can see in the screenshot below, my repository does not have any runners configured.


  1. Navigate to your repository, click on the '<> Code' dropdown and select the 'Codespaces' tab, select the 'Advanced' option to Configure and create codespace. image.png

  2. Select the relevant 'Branch', 'Region', 'Machine type' and for the 'Dev container configuration', select the 'codespaceRunner' config we created and click on 'Createcodespace'. image.png

It takes a few minutes to build and start the container, but you can view the logs whilst the codespace is provisioning in real time.


To speed up codespace creation, repository administrators can enable Codespaces prebuilds for a repository. For more information, see “About GitHub Codespaces prebuilds.”

Once the codespace is provisioned, you can see the hostname of the underlying compute by typing in the terminal: 'hostname'


Navigate to your repository settings page, notice that there is now a self hosted GitHub runner registered and labeled with your user name and repo name. The runner name matches the Codepsace hostname.


Managing Codespace/Runner lifecycle

When you stop your codepsace the self hosted runner will not be removed but will only go into an 'Offline' state, and when you start the codespace up again the runner will be available again.

Also, as mentioned, by default any Active codespaces that are not stopped manually, will be idle and go into a hibernation mode after 30 minutes to save on compute costs. Let’s take a look at how we can amend codespaces lifecycle.

  1. In the upper-right corner of any page, click your profile photo, then click Settings and in the “Code, planning, and automation” section of the sidebar, click Codespaces. image.png

  2. Under “Default idle timeout”, enter the time that you want, then click Save. The time must be between 5 minutes and 240 minutes (4 hours). image.png


As you can see, it is pretty easy to run self hosted action runners inside of your Codespace and utilize the compute power of the dev container itself.

By doing this we can solve a few problems with one solution.

  1. Cost – Not wasting cost and compute power by adding compute separately for self hosted runners alongside Codespaces.
  2. Administration – Having both services running on the same compute and sharing the same configuration and tooling saves time on administration and maintenance.
  3. Availability – Having self hosted runners available as part of the running codespace.

IMPORTANT: Do note that making use of runner labels is very important when triggring/running actions against runners or runner groups provisioned on a Codespace. Hence each runner is labeled with the user name and repo name.


I hope you have enjoyed this post and have learned something new. You can also find the code samples used in this blog post on my published Github page. ❤️


Like, share, follow me on: 🐙 GitHub | 🐧 Twitter | 👾 LinkedIn

Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post

Daha Temiz Kod Yazmak İçin İpuçları

Next Post

Solving RISC-V Kata locally, the not-so-easy way

Related Posts