# 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](/docs/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: ```bash no-run npx 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 no-run # .github/workflows/sandbox-tests.yml name: sandbox-tests on: [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: ```ts check import { 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; ``` ```python check import sys from withruntime import Runtime with 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 ` | 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](https://withruntime.com/account/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. ## Related - [The runtime CLI](/docs/cli#keys-for-ci) for every `keys create` option - [How to call the REST API with curl](/how-to/call-the-rest-api-with-curl) for steps with no Node - [Agent evals and SWE-bench](/use-cases/agent-evals-and-swe-bench) for large parallel runs - [How to turn off sandbox internet](/how-to/turn-off-sandbox-internet) ## Sources Checked 25 September 2026. - [Using secrets in GitHub Actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions) - [Workflow syntax for GitHub Actions](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions) - [Events that trigger workflows](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows) - [actions/setup-node](https://github.com/actions/setup-node) Facts on this page were checked on 25 September 2026.