Blaxel can act as a self-hosted GitHub Actions runner. Each workflow job runs inside an ephemeral micro-VM that is spun up on demand and discarded when the job finishes. The Blaxel GitHub App receives workflow_job events from GitHub and automatically launches your job.
This tutorial assumes that you have a Python project containing unit tests and a GitHub Actions workflow that runs those unit tests using pytest.
Prerequisites
- A Blaxel account and workspace
- A GitHub repository containing a Python project with tests
- The Blaxel CLI (
bl) installed and authenticated
1. Create the Dockerfile
The Dockerfile defines the micro-VM filesystem. A good starting point is the catthehacker Ubuntu image, which includes most tools found on GitHub-hosted runners. Here is an example for a Python test runner:
FROM ghcr.io/catthehacker/ubuntu:full-24.04
USER root
# Install Python
RUN apt-get update -qq \
&& apt-get install -y -qq --no-install-recommends \
python3 python3-pip python3-venv \
&& rm -rf /var/lib/apt/lists/*
# Install pytest
RUN pip3 install pytest pytest-cov pytest-xdist
# Install GitHub Actions runner
ARG RUNNER_VERSION=2.333.0
RUN curl -fSL --retry 3 --retry-delay 5 \
-o /tmp/runner.tar.gz \
"https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz" \
&& mkdir -p /actions-runner \
&& tar xzf /tmp/runner.tar.gz -C /actions-runner \
&& rm /tmp/runner.tar.gz
COPY start.sh /start.sh
RUN chmod +x /start.sh
ENV RUNNER_ALLOW_RUNASROOT=1
ENTRYPOINT ["bash", "/start.sh"]
You can install additional tools or set environment variables using standard RUN, COPY, and ENV instructions. When you are ready to use a different image, edit the Dockerfile and redeploy.
2. Create the entrypoint script
The Dockerfile references an entrypoint script (start.sh) that starts Docker, fetches the JIT config from Blaxel and starts the GitHub Actions runner:
#!/bin/bash
set -euo pipefail
export RUNNER_ALLOW_RUNASROOT=1
RUNNER_DIR="/actions-runner"
export HOME="${RUNNER_DIR}"
TASK_INDEX=${TASK_INDEX:-0}
start_docker() {
command -v dockerd &>/dev/null || return 0
local fs_type="unknown"
local fs_size="unknown"
if mountpoint -q /var/lib/docker 2>/dev/null; then
read -r fs_type fs_size < <(df -hT /var/lib/docker | awk 'NR == 2 {print $2, $3}')
fi
# Docker overlay2 cannot use an overlay filesystem as its backing store.
# That happens when Blaxel overlays a non-empty image path with the volume.
if [ "${fs_type}" = "ext4" ] || [ "${fs_type}" = "xfs" ]; then
STORAGE_DRIVER="overlay2"
echo "Docker storage: ${fs_type} ${fs_size} -> overlay2"
else
STORAGE_DRIVER="vfs"
echo "Docker storage: ${fs_type} ${fs_size} -> vfs"
fi
dockerd --storage-driver="$STORAGE_DRIVER" &>/var/log/dockerd.log &
DOCKERD_PID=$!
for i in $(seq 1 30); do
docker info &>/dev/null && break
if ! kill -0 "$DOCKERD_PID" 2>/dev/null; then
echo "ERROR: dockerd died unexpectedly"
tail -20 /var/log/dockerd.log 2>/dev/null
return 1
fi
sleep 1
done
if docker info &>/dev/null; then
echo "Docker ready ($STORAGE_DRIVER)"
else
echo "ERROR: dockerd failed to start"
tail -20 /var/log/dockerd.log 2>/dev/null
return 1
fi
}
start_docker
# Fetch task arguments from Blaxel execution data
if [ -n "${BL_EXECUTION_DATA_URL:-}" ]; then
echo "Fetching task data from Blaxel..."
TASK_DATA=$(curl -sf "${BL_EXECUTION_DATA_URL}")
JIT_CONFIG=$(echo "${TASK_DATA}" | jq -r ".tasks[${TASK_INDEX}].JIT_CONFIG")
fi
if [ -z "${JIT_CONFIG:-}" ] || [ "${JIT_CONFIG}" = "null" ]; then
echo "Error: JIT_CONFIG not found in task data"
exit 1
fi
cd "${RUNNER_DIR}"
echo "Starting GitHub Actions runner in JIT mode..."
exec ./run.sh --jitconfig "${JIT_CONFIG}"
3. Create blaxel.toml
The blaxel.toml file defines the job configuration. This example uses ephemeral volumes and targets the GitHub repositories this runner is allowed to serve:
type = "job"
name = "pytest-runner"
[runtime]
memory = 16384 # 16 GB of RAM allocated to the micro-VM
timeout = 3600 # maximum job duration: 1 hour (in seconds)
maxRetries = 0 # no automatic retries on failure
diskPercent = 5 # base root disk allocation (percentage)
[githubRunner]
repositories = ["owner/repo"]
[[volumes]]
# Dedicated storage for the Docker daemon
name = "docker"
mountPath = "/var/lib/docker"
type = "ephemeral"
sizeMb = 10240
[[volumes]]
# General-purpose scratch space
name = "tmp"
mountPath = "/tmp"
type = "ephemeral"
sizeMb = 102400
The repositories field lists the GitHub repositories this runner is allowed to pick up jobs from. The format is owner/repo.
4. Deploy the job
Deploy the job to Blaxel:
Due to the large size of the image, the build process can take up to 40 minutes in some cases.
5. Install the Blaxel GitHub App
- Log in to the Blaxel Console.
- Navigate to Hosting > Jobs >
<your-job-name> > Settings.
- Scroll to the GitHub Runner section. It should show as Active.
- Click Edit, then click +.
- Follow the instructions to authorize and install the Blaxel GitHub App on the repository.
- Select the repository and click Save.
6. Redeploy the job
7. Use the runner in your GitHub Actions workflow
In any GitHub Actions workflow, set runs-on to <workspace>/<job-name> to target your Blaxel runner:
name: test
on:
push:
branches:
- main
permissions:
contents: read
jobs:
test:
runs-on: my-blaxel-workspace/pytest-runner
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run tests
id: run-tests
run: |
pytest --tb=short -q
Each time this workflow runs, Blaxel spins up a fresh ephemeral micro-VM, executes the job, and discards the VM when it finishes.
Resources
Jobs reference
Learn more about Blaxel Jobs and configuration options.
blaxel.toml reference
Full reference for all blaxel.toml fields.