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.comFrom 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
geminiin/workspace/.local/bin, the first directory on the sandbox'sPATH, withoutsudo. -pforces non-interactive mode; the task is passed as one array element, so no shell reinterprets its quotes.--skip-trusttrusts the checkout for this session, so the folder-trust check never waits for an answer.--output-format jsonprints one object:responseholds the final answer andstatsthe 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 mcpThat 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 --versionThe 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.
- Gemini CLI on GitHub:
npm install -g @google/gemini-cliandGEMINI_API_KEY - Installation: Node.js 20.0.0 or later
- Headless mode reference: output formats, JSON fields and exit codes
- CLI reference:
--approval-mode,--skip-trust,--include-directories,gemini mcp add - MCP servers:
mcpServersin~/.gemini/settings.json,httpUrland OAuth - @google/gemini-cli on npm: version 0.61.0
Facts on this page were checked on 25 September 2026.