Runtime

How to run Runtime sandboxes from GitHub Actions

Make a key with runtime keys create, store it as the repository secret RUNTIME_API_KEY, and run npx withruntime in a workflow step.

On Runtime a CI job pays for the CPU its tests use, not the time a runner holds a VM. A 2 vCPU, 4 GiB sandbox costs $0.03125 an hour while it waits and $0.08 an hour with both CPUs busy, and a new one ran its first Python command in a median 351 ms on 24 September 2026 (speed). Each job gets a fresh Firecracker microVM with its own kernel, so a test that runs untrusted or agent-written code cannot reach the runner.

1. Make a key for CI

A runner has no browser, so it uses a key. Make one from your own terminal and pipe it straight into the repository's secrets with the GitHub CLI:

Terminalnpx withruntime keys create --name ci --daily-limit 25 | gh secret set RUNTIME_API_KEY

The command prints a link and a code. An owner, admin or developer of the account opens the link, checks the code, sees the key's name, access and limit, and chooses Create key. The key is then printed once, alone on standard output, so it lands in gh secret set and nowhere else. Runtime keeps only a hash of it.

--daily-limit 25 caps what this key may commit in any 24 hours at $25. A job past the cap gets spending_limit_reached and is charged nothing.

2. Add the workflow

YAML# .github/workflows/sandbox-tests.ymlname: sandbox-testson: [push, pull_request]jobs:  test:    runs-on: ubuntu-latest    timeout-minutes: 20    steps:      - uses: actions/checkout@v6      - uses: actions/setup-node@v7        with:          node-version: 24      - name: Run the suite in a sandbox        env:          RUNTIME_API_KEY: ${{ secrets.RUNTIME_API_KEY }}        run: npx withruntime sandbox run --vcpu 2 -- python3 -c 'print(6 * 7)'

sandbox run creates a sandbox, runs the command, prints its output, stops the sandbox and exits with the command's own exit code, so a failing test fails the step. The CLI needs Node 22 or later, which setup-node pins.

3. Ship your code into the sandbox

For a real suite, a short script copies the checkout in and runs it. Call it from a step with npx tsx ci/sandbox.ts or python3 ci/sandbox.py, with the same env block:

TypeScriptimport { Runtime } from "withruntime";// Queue for up to ten minutes when many jobs start at once.const runtime = new Runtime({ waitForCapacityMs: 600_000 });await using sbx = await runtime.sandboxes.create({ timeoutSeconds: 1200, onLeaseEnd: "stop" });await sbx.files.upload(".", "/workspace/repo");const run = await sbx.exec("cd repo && pip install -r requirements.txt && python3 -m pytest -q", {  timeoutMs: 900_000, // the default is 60 seconds  onStdout: (text) => process.stdout.write(text),  onStderr: (text) => process.stderr.write(text),});process.exitCode = run.exitCode ?? 1;
Pythonimport sysfrom withruntime import Runtimewith Runtime(wait_for_capacity=600) as runtime:    with runtime.sandboxes.create(timeout_seconds=1200, on_lease_end="stop") as sbx:        sbx.files.upload(".", "/workspace/repo")        run = sbx.exec(            "cd repo && pip install -r requirements.txt && python3 -m pytest -q",            timeout_ms=900_000,  # the default is 60 seconds        )        print(run.stdout, run.stderr)        sys.exit(0 if run.exit_code == 0 else 1)

await using and with stop the sandbox when the block ends, even after an exception, so a failed job leaves nothing running.

Settings that matter in CI

Setting What it does in a workflow
RUNTIME_API_KEY Overrides any saved connection; the only credential a runner needs
CI (set by GitHub) A missing key is an error instead of a browser approval
--daily-limit on keys create The most the key may commit in 24 hours
--read-only on keys create A key that reads everything and changes nothing, for status checks
waitForCapacityMs / wait_for_capacity How long a create queues for room; two minutes by default
timeoutSeconds, onLeaseEnd: "stop" A hard end for a sandbox a crashed job never stopped
--no-internet, --allow <host> Network rules for the sandbox, set on the host

A paid account runs 100 sandboxes at once to start; the free trial runs eight. A burst of matrix jobs past either limit waits in the SDK instead of failing.

Mistakes and how Runtime handles them

  • Pull requests from forks. GitHub passes no secrets, except GITHUB_TOKEN, to a workflow triggered from a forked repository. The step then has no key, and because CI is set, the CLI fails at once with a missing-key error rather than hanging on a login link.
  • Putting the key in the command line. It would sit in the log and the process list. Pass it only through env, as above; the SDKs and CLI read RUNTIME_API_KEY by themselves.
  • Losing the key. It is printed once. Revoke it at API keys and make another; no key can create, list or revoke keys.
  • A suite that runs past a minute. exec times out after 60 seconds by default and returns timedOut: true with the output so far, so the scripts above raise timeoutMs to 15 minutes; the most is 24 hours.
  • A retried job creating a second sandbox. Every SDK write carries an idempotency key, and timeouts, 429 and 503 are retried with it, so a retried create never makes two sandboxes.
  • Someone leaves the team. Keys they made are revoked with their membership. Make the CI key as a long-standing member, or remake it.

Sources

Checked 25 September 2026.

Facts on this page were checked on 25 September 2026.