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.