Runtime

How to run Gemini CLI in a cloud sandbox

Install @google/gemini-cli with npm in a Linux microVM, keep the Gemini key as a secret, run gemini -p with --approval-mode yolo.

Runtime lets Gemini CLI run with every approval switched on while your Google key stays outside the machine. Each sandbox is a Firecracker microVM with its own Linux kernel, Ubuntu 24.04 and Node.js 24, which meets Gemini CLI's requirement of Node.js 20 or later. The sandbox holds only a placeholder for GEMINI_API_KEY; the host's proxy swaps in the real key on requests to Google's Gemini API and nowhere else. While the agent waits on the model, a 2 vCPU, 4 GiB sandbox bills $0.03125 an hour. Gemini CLI 0.61.0 was the current npm release on 25 September 2026.

Goal Where Gemini CLI runs How it reaches Runtime
Let the agent change a repo with full shell access Inside a sandbox The SDK drives it from your code
Give Gemini CLI on your laptop a safe place to run code On your machine Runtime's MCP server, as a tool set

Why yolo mode needs a sandbox

In headless mode nobody is present to approve a shell command, so a useful run needs --approval-mode yolo, which Google's CLI reference describes as auto-approving all actions. On a laptop or a CI runner that means the model can run anything the user can. In a Runtime sandbox the blast radius is one disposable virtual machine: its own kernel, no route to your network, outbound traffic limited to the hosts you list, and no real credential on its disk.

Keep the Gemini key out of the sandbox

Gemini CLI reads an AI Studio key from GEMINI_API_KEY. Store it once as a Runtime secret tied to the Gemini API's host:

Terminalprintf %s "$GEMINI_API_KEY" | npx withruntime secrets set GEMINI_API_KEY --host generativelanguage.googleapis.com

From then on every sandbox of your account starts with GEMINI_API_KEY set to a value such as rtsec_3f9c…. The proxy puts the real key into the URL or headers of HTTPS requests to generativelanguage.googleapis.com only, so an agent tricked into posting its environment elsewhere sends a string that unlocks nothing (secrets sandboxes never see).

Run a task and collect the diff

TypeScriptimport { writeFile } from "node:fs/promises";import { Sandbox } from "withruntime";const repo = "https://github.com/your-org/your-repo";const task = "Add input validation to the signup handler and write a test for it.";await using sbx = await Sandbox.create({ diskMiB: 8192, timeoutSeconds: 3600 });const setup = { check: true, timeoutMs: 600_000 } as const;await sbx.exec("npm install -g --prefix /workspace/.local @google/gemini-cli", setup);await sbx.exec(["git", "clone", "--depth", "1", repo, "/workspace/app"], setup);// Only the Gemini API and the npm registry are reachable from here.await sbx.network.set({  internet: true,  allow: ["generativelanguage.googleapis.com", "registry.npmjs.org"],});const run = await sbx.exec(  ["gemini", "-p", task, "--approval-mode", "yolo", "--skip-trust", "--output-format", "json"],  { cwd: "/workspace/app", timeoutMs: 1_800_000, onStderr: (text) => process.stderr.write(text) },);const report = JSON.parse(run.stdout);console.log(run.exitCode, report.response, report.stats);await sbx.exec("git add -A && git diff --cached > /workspace/gemini.patch", {  cwd: "/workspace/app",  check: true,});await writeFile("gemini.patch", await sbx.files.readText("/workspace/gemini.patch"));
Pythonimport sys, jsonfrom withruntime import Sandboxrepo = "https://github.com/your-org/your-repo"task = "Add input validation to the signup handler and write a test for it."with Sandbox.create(disk_mib=8192, timeout_seconds=3600) as sbx:    sbx.exec("npm install -g --prefix /workspace/.local @google/gemini-cli",             check=True, timeout_ms=600_000)    sbx.exec(["git", "clone", "--depth", "1", repo, "/workspace/app"],             check=True, timeout_ms=600_000)    sbx.network.set(internet=True,                    allow=["generativelanguage.googleapis.com", "registry.npmjs.org"])    run = sbx.exec(        ["gemini", "-p", task, "--approval-mode", "yolo", "--skip-trust",         "--output-format", "json"],        cwd="/workspace/app", timeout_ms=1_800_000, on_stderr=sys.stderr.write,    )    report = json.loads(run.stdout)    print(run.exit_code, report["response"], report["stats"])    sbx.exec("git add -A && git diff --cached > /workspace/gemini.patch",             cwd="/workspace/app", check=True)    with open("gemini.patch", "w") as file:        file.write(sbx.files.read_text("/workspace/gemini.patch"))

Notes on the run:

  • The install puts gemini in /workspace/.local/bin, the first directory on the sandbox's PATH, without sudo.
  • -p forces non-interactive mode; the task is passed as one array element, so no shell reinterprets its quotes.
  • --skip-trust trusts the checkout for this session, so the folder-trust check never waits for an answer.
  • --output-format json prints one object: response holds the final answer and stats the token use and latency.
  • The patch is written to a file and read back, which keeps a large change clear of the 64 KiB limit on command output.

Headless reference

From Google's headless and CLI reference pages, read on 25 September 2026:

Flag or code Meaning
-p, --prompt Runs one prompt without the terminal UI
--approval-mode yolo Approves every tool call; auto_edit approves edits only
--yolo, -y Deprecated spelling of the same
--output-format json One object with response, stats and error
--output-format stream-json JSONL events: init, message, tool_use, result
--include-directories a,b Adds more folders to the workspace
Exit 0, 1, 42, 53 Success, API error, bad input, turn limit exceeded

Branch on the exit code in your host code: a 53 means the task needs more turns, not that the model failed. For a follow-up task on the same checkout, pause the sandbox between runs; files, memory and processes survive and compute billing stops.

Let Gemini CLI call Runtime

The reverse direction keeps Gemini CLI on your own machine and hands its code execution to Runtime. Add the MCP server once for your user:

Terminalgemini mcp add --scope user runtime npx -y withruntime mcp

That writes an entry to ~/.gemini/settings.json. To edit the file by hand:

JSON{  "mcpServers": {    "runtime": { "command": "npx", "args": ["-y", "withruntime", "mcp"] }  }}

The first tool call prints a link and a short code; approve Connect agent in the browser and the full tool list loads with no key in the file (MCP). A laptop already signed in with npx withruntime login skips that step. Gemini CLI also accepts remote servers by httpUrl, so https://api.withruntime.com/mcp works with browser sign-in instead of the bridge (remote connection).

Tool What Gemini CLI can do with it
runtime_sandbox_create Start a microVM and wait until it is running
runtime_sandbox_exec Run a command and read its exit code and output
runtime_sandbox_interpreter_run Keep a Python or JavaScript session with charts
runtime_sandbox_network_set Cut the sandbox's internet or narrow it to a list
runtime_sandbox_manage Pause, wake, extend or stop it

What a run costs

CPU is billed on use at $0.025 per vCPU-hour, never below a twentieth of a vCPU, and memory at $0.0075 per GiB-hour of what you reserve. That is $0.03125 an hour for 2 vCPUs and 4 GiB while Gemini thinks, rising to $0.08 an hour only while both cores run tests (pricing). Google bills the tokens on your key. A new account has 50 sandbox hours free with no card:

Terminalnpx withruntime sandbox run --trial -- node --version

The same pattern for other agents is in Codex and Qwen Code, which began as a fork of Gemini CLI. A sandbox for coding agents covers running many tasks side by side.

Sources

Checked 25 September 2026.

Facts on this page were checked on 25 September 2026.