Work in progress. Not tested end-to-end. APIs and config schema will change before a stable release.
Alpha — work in progress

GitHub Actions jobs,
each in its own microVM

BurstGrid is an open-source self-hosted runner platform. A central TypeScript scheduler receives GitHub webhooks, dispatches jobs to a fleet of EC2 bare-metal hosts over SSE, and each job boots into a dedicated Firecracker VM in under 200 ms—then disappears.

$ pnpm dev:scheduler [scheduler] listening on :8080 [scheduler] autoscaler started — 3 fleets   # PR opened → GitHub fires workflow_job webhook [webhook] queued — acme/monorepo run#9821 [router] → worker-i-0a3f labels=[linux,large]   # worker receives job over SSE, boots VM [agent] booting VM slot-2 (4 vCPU / 4 GiB) [agent] VM ready in 143ms — runner registered [agent] job completed in 2m 14s — VM destroyed

How it works

From pull request to runner in four hops

What happens from the moment a developer opens a PR to the moment a self-hosted runner picks up the job.

1
GitHub queues the workflow job and fires a webhook
When the PR is opened, GitHub evaluates .github/workflows/ci.yml. Any job with runs-on: [self-hosted, ...] is set to queued and GitHub immediately POSTs a workflow_job event to the webhook URL configured in the BurstGrid GitHub App.
POST /v1/webhook  —  X-GitHub-Event: workflow_job
{ "action": "queued", "workflow_job": { "id": 32145678, "run_id": 9821034, "labels": ["self-hosted", "linux", "burstgrid:size=large"], "status": "queued" }, "repository": { "full_name": "acme/monorepo" } }
2
Scheduler verifies the payload, mints a runner token, enqueues the job
BurstGrid verifies the X-Hub-Signature-256 header using the webhook secret from the GitHub App. It then authenticates as the App installation and calls the GitHub Runners API to generate a one-time runner registration token for the target repo. The job and token go into a priority queue partitioned by tier. If the queue exceeds maxQueueDepth, the scheduler returns 503—GitHub retries for up to 72 hours.
POST /repos/acme/monorepo/actions/runners/registration-token
// one-time token injected into the VM via kernel boot args { "token": "AABCDEFGHIJKLMNOPQ", "expires_at": "2026-08-25T11:30:00Z" }
3
Scheduler dispatches the assignment to a worker over SSE
The router checks each connected worker's free vCPU and RAM against the job's size label (burstgrid:size=large → 4 vCPU / 4 GiB). The first worker with matching capabilities and enough headroom receives the job as a JSON event on its persistent GET /v1/workers/:id/stream SSE connection. No polling, no database.
SSE → worker-i-0a3f2b  (persistent connection)
data: { "jobId": "3d9f...", "runnerToken": "AABCDEFGHIJKLMNOPQ", "labels": ["self-hosted", "linux", "burstgrid:size=large"], "vcpus": 4, "memoryMiB": 4096 }
4
Worker boots a Firecracker microVM, runner self-registers, job runs
The agent calls the Firecracker Unix socket API to boot a microVM with the requested resources. The runner token and labels are passed as kernel boot arguments—the Actions runner inside the VM reads /proc/cmdline, registers with GitHub, and picks up the queued job. On completion the VM is terminated, the slot freed, and the worker sends a heartbeat back with updated resource counts. The rootfs is never reused.
Firecracker API → PUT /boot-source
{ "kernel_image_path": "/opt/burstgrid/vmlinux", "boot_args": "console=ttyS0 reboot=k panic=1 RUNNER_TOKEN=AABCDEFGHIJKLMNOPQ RUNNER_LABELS=self-hosted,linux,burstgrid:size=large" }

Setup

Yes, BurstGrid needs a GitHub App

The App provides a signed webhook channel and an installation token so the scheduler can call the Runners API to mint the per-repo registration tokens that let VMs self-register with GitHub.

Permissions required

  • Read & writeAdministration — create and delete self-hosted runners on repos/orgs
  • ReadActions — read workflow run details
  • ReadMetadata — required by GitHub for all Apps
  • WebhookSubscribe to workflow_job events — queued, in_progress, completed

Environment variables

VariableWhat it is
GITHUB_APP_IDNumeric App ID from the App settings page
GITHUB_PRIVATE_KEYPEM private key — signs JWTs for API auth
GITHUB_WEBHOOK_SECRETSet when creating the App — used to verify X-Hub-Signature-256
GITHUB_INSTALLATION_IDInstallation ID for the org the App is installed on

Create the App in five steps

1. Settings → Developer Settings → GitHub Apps → New GitHub App 2. Set webhook URL to https://your-scheduler/v1/webhook and generate a webhook secret 3. Grant the permissions above and subscribe to workflow_job events 4. Generate a private key — download the .pem and set it as GITHUB_PRIVATE_KEY 5. Install the App on your org — note the installation ID in the URL

What's inside

Built for teams who've hit the ceiling

GitHub-hosted runners top out at 4 vCPU / 16 GiB. BurstGrid hands you the metal.

Firecracker isolation

Every job gets its own kernel. No shared namespaces, no container breakout surface. The rootfs is discarded the moment the runner exits.

SSE dispatch

Workers hold a persistent Server-Sent Events connection. Jobs arrive the instant they're queued—no polling, no long-poll timeouts, no extra infra.

Resource-aware routing

Label jobs with burstgrid:size=large. The scheduler only assigns to workers with enough free vCPU and RAM. No overcommit.

Per-tier autoscaling

Define separate EC2 launch templates for standard, large, and xlarge fleets. Each scales independently against its own queue threshold.

Circuit breaker

GitHub API calls trip after 5 consecutive failures and stay open for 30 s. Returns 503 so GitHub keeps retrying for up to 72 hours.

OpenTelemetry metrics

Queue depth, dispatch latency, VM boot time, and job duration—all OTLP. Drop the included OTel Collector config on each host and ship to Grafana or Datadog.