Kedge runs GitHub Actions jobs in disposable hardware-isolated VMs:
jobs:
test:
runs-on: kedge
steps:
- uses: actions/checkout@v6
- run: make test
The repository only needs access to the runner integration; it does not need to deploy as a Kedge app.
enable runners
GitHub exposes different runner permissions for organizations and personal accounts.
organization repositories
Use the main Kedge GitHub App. Install it on the organization, select the repositories that may use Kedge, and approve Self-hosted runners: read and write. An organization owner must approve the installation and permission.
Enable the organization pool:
kedge runners enable my-organization
kedge runners status my-organization
Kedge creates a restricted kedge runner group. Its repository list tracks
the App installation's selected repositories.
personal repositories
GitHub's repository-level runner API requires Administration: read and write. Kedge keeps that permission in a separate, runner-only App.
Open install the Kedge Runner App, choose the account, and select the repositories that may use Kedge. Enable each repository by its full name:
kedge runners enable owner/repository
kedge runners status owner/repository
The main Kedge App is optional unless the repository also deploys to Kedge. The user who installs the runner App and enables the repository must be able to sign in to Kedge; other workflow authors do not need Kedge accounts.
job lifecycle
GitHub queues matching jobs behind runs-on: kedge; Kedge starts one VM per
assigned job. Jobs remain queued while the configured Kedge concurrency is
busy.
Individual runners are ephemeral and may appear in GitHub only while a job is assigned. For an organization repository, inspect Settings → Actions → Runner groups at the organization level. The repository's Self-hosted runners page may appear empty while the pool is working correctly.
runner environment
Each job gets a fresh Ubuntu 24.04 x86-64 VM with 4 vCPUs, 8 GiB of memory, and
50 GiB of temporary scratch space. The image includes Git, Git LFS, jq,
common archive and build tools, Python 3, Docker, Buildx, and Docker Compose.
The runner user has passwordless sudo.
The VM runs one job and is destroyed. Checkouts, Docker layers, installed tools, and scratch data do not carry into the next job. Store durable output with GitHub artifacts, caches, packages, or another external service.
The image is not byte-for-byte compatible with GitHub's ubuntu-latest.
Declare or install required tools rather than relying on undocumented
preinstalled versions.
network boundaries
Jobs can initiate public IPv4 connections. They cannot accept inbound connections, reach Kedge's host or private workload networks, contact link-local or cloud metadata addresses, or use IPv6. Service containers remain reachable from other steps through the job's local Docker network.
Use a narrow authenticated public endpoint for private dependencies, or run that job on a runner with the required network placement.
untrusted code
Public repositories may use the pool, but a fork workflow consumes the repository owner's Kedge capacity once GitHub allows it to run.
Configure Approval for running fork pull request workflows from contributors under Settings → Actions → General. Workflow code can read any secret GitHub supplies to the job, so retain GitHub's fork protections and review privileged workflows.
GitHub documents fork-workflow approval and Actions hardening.
verify
Run this workflow before moving a real CI pipeline:
name: kedge runner smoke test
on:
workflow_dispatch:
jobs:
smoke:
runs-on: kedge
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- name: Inspect runner
run: |
set -euxo pipefail
test "$(uname -m)" = x86_64
test "$(nproc)" -eq 4
df -h / /home/runner/_work /var/lib/docker
curl -fsS https://api.github.com/meta >/dev/null
- name: Exercise Docker
run: |
docker version
docker run --rm hello-world
Trigger it from the repository's Actions tab. While it runs,
kedge runners status <organization> or kedge runners status owner/repository shows the active job. Run it again to confirm the runner and
filesystem are fresh.
troubleshooting
The job stays queued. Confirm runs-on: kedge. For an organization, check
the kedge group under the organization's Settings → Actions → Runner
groups, including pending App permissions and repository access. For a
personal repository, check the runner App installation. Jobs also queue when
Kedge capacity is busy.
runners enable cannot find an installation. For an organization, finish
the main App installation while signed in to the same Kedge account. For a
personal repository, install the runner App on that repository and use its
full owner/repository name.
A command exists on ubuntu-latest but not here. Add the corresponding
setup-* action or an installation step.
The job needs a private address or IPv6. That traffic is blocked. Use a public authenticated endpoint or a differently placed runner.
See GitHub's documentation for runner-group access and workflow labels.
disable runners
Disable the pool before removing App access:
kedge runners disable my-organization
kedge runners disable owner/repository
This deletes the GitHub-side scale set and stops local listeners and VMs. Disabling runners does not disconnect repositories or change automatic deploys.
If App access was removed first and cleanup fails, restore access, run
runners disable, then remove access again.