The modbox/sandbox Image

Full desktop sandbox with Chromium, VNC, terminal and REST API
View as Markdown

The modbox/sandbox image is a full-featured, browser-capable sandbox. It runs a complete Ubuntu desktop environment inside a container and exposes everything — CDP, VNC, a web terminal, a file/exec API, and MCP endpoints — behind a single HTTPS URL.

Use this image when your workload needs a real browser, visual interaction, or a persistent remote desktop. For headless code execution without a browser, use modbox/sandbox-lite instead.

What is inside

Container (Ubuntu 22.04)
|
+-- Xvfb :99 virtual X11 display (1920x1080)
+-- x11vnc VNC server connected to the display
+-- noVNC :6080 browser-based VNC client (WebSocket)
+-- Chromium :9222 browser with Chrome DevTools Protocol
+-- cdp-mcp-gateway CDP exposed as an MCP server
+-- supergateway :6060 bash shell exposed as an MCP server
+-- ttyd :3000 web terminal
+-- sandbox-server :3334 REST API for exec and file upload/download
+-- nginx :8080 main entry point (all routes below)

All processes are managed by supervisord and restart automatically.

Provisioning

1import { ModboxClient } from "modbox-sdk";
2
3const modbox = new ModboxClient({ token: process.env.MODBOX_API_TOKEN });
4
5const sandboxApiKey = crypto.randomUUID(); // you generate this
6
7await modbox.provisionSandbox({
8 taskId: "my-browser-sandbox",
9 imageId: process.env.MODBOX_SANDBOX_IMAGE_ID, // modbox/sandbox image ID
10 ttlSeconds: 1800,
11 envVars: {
12 SANDBOX_API_KEY: sandboxApiKey, // required to authenticate the REST API
13 },
14});
15
16await modbox.waitForSandbox({ taskId: "my-browser-sandbox", timeout: 120 });
17const sandbox = await modbox.getSandbox("my-browser-sandbox");
18// sandbox.sandboxUrl → https://my-browser-sandbox.sandbox.modbox.run

The SANDBOX_API_KEY is a secret you generate. It is injected into the container via env_vars and required to authenticate every call to the sandbox REST API (/modbox/api/*). Store it alongside sandboxUrl in your session.

Routes

All routes are served through nginx at https://{sandbox_url} (port 8080).

PathDescription
/Redirects to /vnc/ — opens the desktop in the browser
/vnc/noVNC — view and control the desktop from a browser tab
/terminal/ttyd — web-based bash terminal (WebSocket)
/cdp/Chrome DevTools Protocol HTTP endpoint (JSON/version, JSON/list)
/devtools/CDP WebSocket endpoint (used by Playwright, Puppeteer, etc.)
/mcp/terminal/MCP server for bash shell (SSE / Streamable HTTP)
/mcp/cdp/MCP server for browser control via CDP (SSE / Streamable HTTP)
/modbox/api/execREST — execute shell commands
/modbox/api/filesREST — upload, list, and download files

Executing commands

Use POST /modbox/api/exec to run a shell command inside the sandbox. The endpoint executes arbitrary bash and returns stdout, stderr, exit code and duration.

$POST https://{sandbox_url}/modbox/api/exec
$Authorization: Bearer {SANDBOX_API_KEY}
$Content-Type: application/json
$
${
> "command": "python3 -c \"print('hello')\"",
> "timeout": 10000,
> "cwd": "/workspace"
>}

Response:

1{
2 "exitCode": 0,
3 "stdout": "hello\n",
4 "stderr": "",
5 "killed": false,
6 "signal": null,
7 "durationMs": 38,
8 "truncated": false
9}

Fields:

FieldTypeDescription
commandstringShell command (required)
timeoutintegerTimeout in milliseconds — default and max is 30 000 ms
cwdstringWorking directory — default is /
1const result = await fetch(`${sandbox.sandboxUrl}/modbox/api/exec`, {
2 method: "POST",
3 headers: {
4 "Content-Type": "application/json",
5 "Authorization": `Bearer ${sandboxApiKey}`,
6 },
7 body: JSON.stringify({
8 command: "python3 script.py",
9 cwd: "/workspace",
10 timeout: 15000,
11 }),
12}).then((r) => r.json());
13
14console.log(result.stdout); // → output of the script
15console.log(result.exitCode); // → 0 if successful

Uploading and downloading files

Upload

POST /modbox/api/files/upload — multipart/form-data, field name file. Max 50 MB.

$curl -X POST https://{sandbox_url}/modbox/api/files/upload \
> -H "Authorization: Bearer {SANDBOX_API_KEY}" \
> -F "file=@/local/path/script.py"

Response:

1{
2 "filename": "1748000000000-script.py",
3 "originalName": "script.py",
4 "size": 1024,
5 "mimetype": "text/x-python",
6 "uploadedAt": "2026-05-22T10:00:00.000Z"
7}

After uploading, the file is stored in /workspace/uploads/{filename} inside the container. Execute it with the exec endpoint.

List files

$GET https://{sandbox_url}/modbox/api/files
$Authorization: Bearer {SANDBOX_API_KEY}
$
$# Optional: filter by name
$GET https://{sandbox_url}/modbox/api/files?search=script

Download

$GET https://{sandbox_url}/modbox/api/files/download/{filename}
$Authorization: Bearer {SANDBOX_API_KEY}

Delete

$DELETE https://{sandbox_url}/modbox/api/files/{filename}
$Authorization: Bearer {SANDBOX_API_KEY}

Browser automation (CDP)

The sandbox runs a Chromium instance with Chrome DevTools Protocol enabled. Connect Playwright, Puppeteer, or any CDP client using the /cdp/ HTTP endpoint. Nginx rewrites the WebSocket URLs in CDP responses so they resolve correctly through the proxy.

1import { chromium } from "playwright";
2
3// connectOverCDP expects an HTTP endpoint, not WebSocket
4const browser = await chromium.connectOverCDP(
5 `https://${new URL(sandbox.sandboxUrl).host}/cdp`
6);
7
8const context = browser.contexts()[0];
9const page = context.pages()[0];
10
11await page.goto("https://example.com");
12const title = await page.title();
13
14await browser.close();

Pass the HTTP endpoint (https://.../cdp) to connectOverCDP, not a WebSocket URL. Playwright fetches {endpoint}/json/version to discover the actual WebSocket debugger URL, which nginx rewrites to route correctly through the proxy.

Visual desktop (noVNC)

Open {sandbox_url}/vnc/ in a browser to access a full graphical desktop and watch the browser interact in real time. Useful for debugging automations, recording sessions, or running GUI applications.

No credentials required to access the VNC viewer — access is controlled by the sandbox URL itself, which is only known to the provisioning party.

Web terminal

Open {sandbox_url}/terminal/ in a browser to access a bash terminal running as the sandbox user inside the container. The terminal streams over WebSocket via ttyd.

MCP integration

The sandbox exposes two MCP servers, both available from the sandbox URL:

MCP serverPathCapability
Terminal (bash)/mcp/terminal/Run shell commands, read/write files
CDP browser/mcp/cdp/Control the browser via DevTools Protocol

Connect any MCP-compatible client (Claude Desktop, VS Code, custom agent) by pointing it at https://{sandbox_url}/mcp/terminal/ or https://{sandbox_url}/mcp/cdp/.

1{
2 "mcpServers": {
3 "sandbox-terminal": {
4 "url": "https://my-browser-sandbox.sandbox.modbox.run/mcp/terminal/"
5 },
6 "sandbox-browser": {
7 "url": "https://my-browser-sandbox.sandbox.modbox.run/mcp/cdp/"
8 }
9 }
10}

Proxy support

If your sandbox needs to route traffic through a corporate proxy, pass the proxy settings via env_vars. The entrypoint propagates them automatically to apt, pip, git, and npm.

1{
2 "envVars": {
3 "HTTP_PROXY": "http://proxy.example.com:3128",
4 "HTTPS_PROXY": "http://proxy.example.com:3128",
5 "NO_PROXY": "localhost,127.0.0.1"
6 }
7}

For SOCKS5 proxies, replace the value with socks5://proxy.example.com:1080.

sandbox vs sandbox-lite

Featuremodbox/sandboxmodbox/sandbox-lite
Desktop (Xvfb + VNC)YesNo
Chromium + CDPYesNo
noVNC browser viewerYesNo
Web terminal (ttyd)YesNo
sandbox-server (exec + files)YesYes
MCP shell serverYesYes
nginxYesYes
Typical memory1.5–2 GB128–256 MB

Use modbox/sandbox when the task needs a real browser or visual interaction. Use modbox/sandbox-lite for code execution, scripts, and automation without a GUI.