# 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: ```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, 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 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@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: ```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` 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 ` | 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](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. 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. ## 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 26 October 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 26 October 2026.