Provider-neutral cloud Chrome runtime for AI agents and browser automation tools.
Supports:
/) for Streamable HTTP clientsBuild:
docker build -t chrome-sandbox:local .
Run:
docker run --rm -it \
-p 6080:6080 \
-p 5900:5900 \
-p 9222:9222 \
-p 9515:9515 \
-p 5800:5800 \
-p 5901:5901 \
-e AUTH_TOKEN="change-me" \
chrome-sandbox:local
Open noVNC at (token required):
http://127.0.0.1:6080/vnc.html?token=change-meUse the included compose file:
docker compose up --build -d
File:
docker-compose.ymlBy default it exposes:
6080 noVNC (token-gated)9222 CDP (token-gated)9515 WebDriver (token-gated)5800 Admin API (token-gated)5901 MCP Streamable HTTP (token-gated)Set a strong value for AUTH_TOKEN in docker-compose.yml before production usage.
/home/chrome/.config/chromium/state (token and proxy state)To prevent data loss, persist both /home/chrome and /state.
The included compose and Kubernetes examples are wired for this.
You can run custom scripts during container lifecycle to support external orchestration systems (for example Grabba-style self-registration).
Hook env vars:
PRE_START_HOOK - runs once before supervisor starts servicesPOST_START_HOOK - runs once after Chrome becomes healthyPRE_STOP_HOOK - runs once when container receives shutdown signalPOST_STOP_HOOK - runs after PRE_STOP_HOOKHOOK_TIMEOUT_SECONDS - per-hook timeout (default 30)HOOK_REQUIRED - if true, startup fails when pre/post start hook fails (default false)CHROME_READY_TIMEOUT_SECONDS - max wait for Chrome health before POST_START_HOOK (default 60)Behavior:
AUTH_TOKEN, endpoint ports, etc.).Example compose snippet:
environment:
PRE_START_HOOK: "/hooks/pre-start.sh"
POST_START_HOOK: "/hooks/post-start.sh"
PRE_STOP_HOOK: "/hooks/pre-stop.sh"
POST_STOP_HOOK: "/hooks/post-stop.sh"
HOOK_TIMEOUT_SECONDS: "30"
HOOK_REQUIRED: "false"
volumes:
- ./hooks:/hooks:ro
Grabba mapping:
POST_START_HOOKPRE_STOP_HOOKPOST_STOP_HOOKApply:
kubectl apply -f k8s/chrome-sandbox.yaml
Manifest includes:
AUTH_TOKEN)/home/chrome and /stateTo expose externally, add your ingress or switch the service type (for example LoadBalancer) based on your cluster setup.
Set PVC storage class names in the manifest if your cluster requires explicit storage class selection.
http://<host>:6080http://<host>:9222http://<host>:9515http://<host>:5800http://<host>:5901/chromium-sandbox to support sandboxed launches in hardened environments.--no-sandbox so service still comes up.CHROME_NO_SANDBOX=true only when your container runtime requires it.CHROME_NO_SANDBOX=true can show Chrome's "unsupported command line flag --no-sandbox" warning banner.When admin, noVNC, and MCP are exposed on different hosts, set these env vars so the admin landing page generates correct links and MCP config snippets:
PUBLIC_ADMIN_BASE_URL (optional; default uses current admin page origin)PUBLIC_VNC_BASE_URL (default http://localhost:6080)PUBLIC_MCP_BASE_URL (default http://localhost:5901)This image now includes a built-in MCP server.
Use Streamable HTTP against http://<host>:5901 with the same token auth used elsewhere.
curl -sS http://127.0.0.1:5901 \
-H "Authorization: Bearer change-me" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"id":1,
"method":"initialize",
"params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"demo","version":"1.0.0"}}
}'
curl -sS http://127.0.0.1:5901 \
-H "Authorization: Bearer change-me" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
Open URL:
curl -sS http://127.0.0.1:5901 \
-H "Authorization: Bearer change-me" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"id":3,
"method":"tools/call",
"params":{"name":"open_url","arguments":{"url":"https://example.com"}}
}'
# 1) Navigate
curl -sS http://127.0.0.1:5901 \
-H "Authorization: Bearer change-me" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"open_url","arguments":{"url":"https://example.com/form"}}}'
# 2) Type into an input
curl -sS http://127.0.0.1:5901 \
-H "Authorization: Bearer change-me" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"type","arguments":{"selector":"input[name=\"email\"]","text":"[email protected]"}}}'
# 3) Click submit
curl -sS http://127.0.0.1:5901 \
-H "Authorization: Bearer change-me" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"click","arguments":{"selector":"button[type=\"submit\"]"}}}'
# 4) Wait for success UI then extract text
curl -sS http://127.0.0.1:5901 \
-H "Authorization: Bearer change-me" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"wait_for_selector","arguments":{"selector":".success-message","visible":true,"timeoutMs":15000}}}'
curl -sS http://127.0.0.1:5901 \
-H "Authorization: Bearer change-me" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"extract_text","arguments":{"selector":".success-message"}}}'
Configure Cursor to call this server over HTTP:
{
"mcpServers": {
"cloudChrome": {
"transport": {
"type": "streamable_http",
"url": "http://127.0.0.1:5901",
"headers": {
"Authorization": "Bearer change-me"
}
}
}
}
}
Point Claude to the same Streamable HTTP endpoint:
{
"mcpServers": {
"cloudChrome": {
"transport": {
"type": "streamable_http",
"url": "http://127.0.0.1:5901",
"headers": {
"Authorization": "Bearer change-me"
}
}
}
}
}
You can also connect tools directly:
http://<host>:9222 with token headerhttp://<host>:9515 with token headerFor direct CDP clients that cannot set headers, use token query param where supported by your client path.
All exposed endpoints are token-gated.
Use either:
Authorization: Bearer <AUTH_TOKEN>X-Auth-Token: <AUTH_TOKEN>?token=<AUTH_TOKEN> (recommended for browser noVNC access)If AUTH_TOKEN is not provided, one is generated at startup and saved to /state/auth_token in the container.
If /state/auth_token already exists and AUTH_TOKEN env is unset, startup reuses that persisted token.
That means endpoints still require a token; clients just need to read the generated one from /state/auth_token if you did not set AUTH_TOKEN explicitly.
No proxy is required. By default, Chrome uses direct egress.
Startup proxy configuration:
PROXY_URL (example: http://username:[email protected]:8080)PROXY_BYPASS (optional Chrome bypass list)This works with Webshare and any provider that exposes a standard HTTP/HTTPS proxy endpoint.
Set or update proxy:
curl -X POST http://127.0.0.1:5800/proxy \
-H "Authorization: Bearer change-me" \
-H "Content-Type: application/json" \
-d '{"proxyUrl":"http://user:[email protected]:8080","bypassList":"localhost,127.0.0.1"}'
Disable proxy:
curl -X DELETE http://127.0.0.1:5800/proxy \
-H "Authorization: Bearer change-me"
Read proxy state:
curl http://127.0.0.1:5800/proxy \
-H "Authorization: Bearer change-me"
GET /health (token required)GET / (public landing page with token helper UI, route links, and MCP config snippet)GET /session (token required)GET /proxy (token required)POST /proxy (token required)DELETE /proxy (token required)GET /session is for orchestration state discovery and currently includes:
session_infolist_tabsnew_tabswitch_tabclose_tabopen_urlget_htmlclicktypewait_for_selectorwait_for_navigationextract_textevaluate_jsscreenshotget_proxyset_proxyclear_proxyAUTH_TOKEN values.VNC_PASSWORD to protect VNC access.Content type
Image
Digest
sha256:e8af2801a…
Size
411.3 MB
Last updated
about 1 month ago
docker pull itsobaa/chrome-sandbox