Skip to content
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. Then run npx withruntime in a workflow step.

On Runtime, a CI job pays for the CPU its tests use, not for the time a runner holds a VM. A 2 vCPU, 4 GiB sandbox costs $0.03125 an hour while it waits. With both CPUs busy, it costs $0.08 an hour. 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 directly 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, reviews the key's name, access and limit, and chooses Create key. The key is then printed once, alone, on standard output. So it goes to gh secret set and nowhere else. Runtime keeps only a hash of the key.

--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 not charged.

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@v7      - 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 sandboxes run --vcpu 2 -- python3 -c 'print(sum([125, 250, 375]))'

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

3. Ship your code into the sandbox

For a real suite, use a short script that copies the checkout into the sandbox and runs it. Call the script 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 delete 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 the same time. An account without credit runs four. 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. Because CI is set, the CLI fails immediately with a missing-key error, rather than waiting on a login link.
  • Putting the key in the command line. The key would appear 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. It returns timedOut: true with the output so far. So the scripts above raise timeoutMs to 15 minutes. The maximum is 24 hours.
  • A retried job creating a second sandbox. Every SDK write carries an idempotency key. Timeouts, 429 and 503 responses are retried with that key. So a retried create never makes two sandboxes.
  • Someone leaves the team. Keys they made are revoked when their membership ends. Create the CI key as a long-standing member, or make a new one.

Sources

Checked 26 October 2026.

Facts on this page were checked on 26 October 2026.

100 free hoursin your first 30 days.

of a 1 GB machine

  • No credit card
  • Four sandboxes at once, 2 vCPU and 4 GiB each
  • Then prepaid credit from $10, no plan fee
Start free, no card
Start free, no card