---
title: "A private Chrome in the cloud, for you and your agents"
description: "Run Chrome or Edge on an exe.dev VM: use it in a browser tab behind exe.dev login, and drive the same browser from Playwright, Puppeteer and agents over CDP."
url: https://exe-staging.dev/deploy/cloud-browser
category: "AI agents"
updated: 2026-10-31
---

# A private Chrome in the cloud, for you and your agents

A remote Chrome you open in a tab and your scripts drive over CDP.

- **Software:** Cloud browser (https://docs.linuxserver.io/images/docker-chrome/)
- **Source:** https://github.com/linuxserver/docker-chrome
- **License:** LinuxServer.io images GPL-3.0, Selkies MPL-2.0, Playwright MCP Apache-2.0, Lightpanda AGPL-3.0; Chrome and Edge proprietary
- **Time:** 5 minutes
- **Example VM name:** `my-browser` (yours will differ; on the VM, `hostname` prints it)
- **Machine-readable:** https://exe-staging.dev/deploy/cloud-browser/manifest.json
- **File:** https://exe-staging.dev/deploy/cloud-browser/setup.sh

## What you get

- Google Chrome running all the time in a container, restarted if it closes or crashes and after a reboot
- The browser at `https://<vm>.exe.xyz:3000/`, behind exe.dev login, with sound, clipboard and file upload and download
- Chrome's DevTools protocol on `localhost:9222` and, behind exe.dev login, at `https://<vm>.exe.xyz:9222/`
- Playwright MCP on `localhost:8931`, registered with Claude Code, driving the same browser
- Logins, cookies and tabs kept in `~/browser/config` on the VM's disk

Running on the VM:

- **Google Chrome**: in LinuxServer.io's Selkies container (or Edge or Chromium), profile on the VM's disk, data in `~/browser/config`
- **Selkies**: streams the browser to a tab, with sound, clipboard and file transfer, port 3000
- **Chrome DevTools protocol (CDP)**: for Playwright, Puppeteer and agents, through a small nginx, port 9222
- **Playwright MCP**: browser tools for Claude Code, Codex and other agents on the VM, on localhost only, port 8931

How it is reached:

- https://my-browser.exe.xyz:3000/ (private): the browser, in a tab, behind exe.dev login
- https://my-browser.exe.xyz:9222/ (private): CDP for scripts elsewhere, with an exe.dev token; or `ssh -L 9222:localhost:9222`
- http://localhost:9222 (private): CDP for scripts and agents on the VM

## Deploy with Shelley

Open https://exe-staging.dev/new?prompt=Set%20up%20Cloud%20browser%20on%20this%20VM%20by%20following%20the%20exe.dev%20recipe%20at%20https%3A%2F%2Fexe-staging.dev%2Fdeploy%2Fcloud-browser.md%20from%20top%20to%20bottom.%0A%0AThe%20recipe%27s%20example%20VM%20name%20is%20my-browser.%20This%20VM%27s%20name%20is%20the%20output%20of%20%60hostname%60%3A%20use%20it%20wherever%20the%20recipe%20writes%20my-browser.%20Skip%20the%20step%20that%20creates%20the%20VM%3B%20you%20are%20on%20it.%0A%0AYou%20are%20done%20when%20all%20of%20these%20are%20true.%20Check%20each%20one%20yourself%3A%0A-%20Google%20Chrome%20running%20all%20the%20time%20in%20a%20container%2C%20restarted%20if%20it%20closes%20or%20crashes%20and%20after%20a%20reboot%0A-%20The%20browser%20at%20%60https%3A%2F%2F%3Cvm%3E.exe.xyz%3A3000%2F%60%2C%20behind%20exe.dev%20login%2C%20with%20sound%2C%20clipboard%20and%20file%20upload%20and%20download%0A-%20Chrome%27s%20DevTools%20protocol%20on%20%60localhost%3A9222%60%20and%2C%20behind%20exe.dev%20login%2C%20at%20%60https%3A%2F%2F%3Cvm%3E.exe.xyz%3A9222%2F%60%0A-%20Playwright%20MCP%20on%20%60localhost%3A8931%60%2C%20registered%20with%20Claude%20Code%2C%20driving%20the%20same%20browser%0A-%20Logins%2C%20cookies%20and%20tabs%20kept%20in%20%60~%2Fbrowser%2Fconfig%60%20on%20the%20VM%27s%20disk%0A%0ARun%20the%20VM-side%20command%20in%20%22Set%20up%20by%20hand%22%20%28skip%20creating%20the%20VM%29%2C%0Athen%20the%20commands%20in%20%22Let%20an%20agent%20drive%20it%22%20that%20connect%20Claude%20Code%0Ato%20Playwright%20MCP%2C%20and%20check%20them%20with%20the%20%60claude%20-p%60%20command%20there.%0AIf%20Claude%20Code%20settings%20already%20exist%2C%20add%20the%20MCP%20server%20without%0Areplacing%20them.%20Never%20make%20the%20VM%20public%20or%20point%20port%208000%20at%20port%0A3000%20or%209222%3A%20the%20browser%20has%20no%20password%20of%20its%20own.%20Finish%20with%20the%0Abrowser%27s%20URL.%0A%0AFinish%20with%20the%20URLs%20I%20should%20open.%0A to create a VM whose agent, Shelley, follows this recipe. From a terminal:

```sh
curl -fsSL https://exe-staging.dev/deploy/cloud-browser/prompt.txt | ssh exe.dev new --prompt=/dev/stdin
```

Or follow the steps below by hand.

Sometimes you just need a browser somewhere else: one that stays signed
in to the dashboards you check every day, that keeps running when you
close your laptop, that a script or an AI agent can drive while you
watch, or that opens the link you'd rather not open on your own machine.
This recipe puts Google Chrome on an exe.dev VM. You use it in a browser
tab, with sound, clipboard and file transfer, and programs drive the same
browser through Chrome's DevTools protocol (CDP): Playwright, Puppeteer,
or Claude Code and Codex through Playwright MCP.

It's private without any work: the tab is behind exe.dev login, so
there's no password to set and no public URL to guard. Logins and cookies
live on the VM's disk and survive restarts. Microsoft Edge or Chromium
instead of Chrome is one setting. If you need a whole desktop rather than a
browser, see [Linux desktop](/deploy/linux-desktop) or
[Computer use](/deploy/computer-use).

## Set up the browser

Deploy does this for you. By hand, it's one script that starts the
browser and its helpers with Docker Compose.

<details id="create-the-vm" data-manual-setup class="dp-details">
<summary>Set up by hand</summary>

On your computer, create a VM and connect. Pick any free name; if you've
already clicked Deploy or have a VM, skip the first command and use its
name:

```bash
ssh exe.dev new --name=my-browser
ssh my-browser.exe.xyz
```

On the VM, read [setup.sh](https://exe-staging.dev/deploy/cloud-browser/setup.sh),
then run it:

```bash
curl -fsSL https://exe-staging.dev/deploy/cloud-browser/setup.sh | bash
```

For Microsoft Edge, put `BROWSER=edge` in front of `bash`; for Chromium,
`BROWSER=chromium`. `TZ=Europe/Berlin` (or your own time zone) sets the
browser's clock, which defaults to UTC.

It takes about a minute, most of it downloading, and ends with the
browser's version and its addresses. Everything is in `~/browser`:

| Path | What it is |
| --- | --- |
| `compose.yaml` | three containers: `browser`, LinuxServer.io's [Chrome image](https://docs.linuxserver.io/images/docker-chrome/) with Selkies on port 3000; `cdp`, an nginx that passes CDP to port 9222; and `mcp`, Microsoft's [Playwright MCP](https://github.com/microsoft/playwright-mcp) on `localhost:8931` |
| `config/` | the browser's home: its profile in `config/profile`, downloads in `config/Downloads` |
| `cdp.conf` | the nginx config |
| `close-browser.py`, `init/` | small scripts that close the browser cleanly when the container stops, and clear a stale profile lock when it starts |

The containers restart if they stop and come back after a reboot. If you
close the browser's last window, it opens again a second later. Running
the script again updates the images and keeps the profile.

</details>

## Open it in a tab

Open `https://my-browser.exe.xyz:3000/` and sign in to exe.dev if asked.
Chrome fills the tab and follows its size. Sign in to the sites you use;
the browser keeps the cookies on the VM's disk, so you stay signed in
across restarts and from any computer you open the tab on.

<figure>
<img src="https://exe-staging.dev/deploy/cloud-browser/browser-tab.webp" alt="Chrome on an exe.dev VM, shown in a browser tab at 1280×800, with Selkies' sidebar open on the left showing its stream controls and stats: AV1 software encoding, WebSockets, 14 ms latency" width="1280" height="800" loading="lazy">
<figcaption>Chrome on the VM, in a tab. The sidebar on the left, from the handle at the screen's edge, has sound, clipboard, file transfer and stream settings.</figcaption>
</figure>

The tab is a video stream of the browser, encoded in software and sent
over a WebSocket; your clicks and keys go back the same way. Reading,
typing and scrolling feel close to local, and video plays with sound.
The handle at the left edge opens Selkies' sidebar:

- **Clipboard**: text you copy on either side shows up on the other.
- **Files**: upload files to the browser's Downloads folder, and download
  what it saved. They're also in `~/browser/config/Downloads` on the VM.
- **Video settings**: codec, frame rate and quality, if the stream is
  slow on your connection.
- **Fullscreen**, and buttons for sound, microphone and webcam.

To give a teammate the same browser, share the VM with them, on your
computer: `ssh exe.dev share add my-browser teammate@example.com`. They
sign in to exe.dev and open the same address, without getting a shell
on the VM. Two people in the tab share one mouse and keyboard.

> [!WARNING]
> The browser has no password of its own; exe.dev login is the lock.
> Don't point port 8000 at it with `share port` and `share set-public`,
> or anyone could use it, signed in as you.

## Drive it from code

On the VM, Chrome's DevTools protocol is at `http://localhost:9222`.
Playwright and Puppeteer attach to the running browser, its tabs, and
its signed-in sessions, and you watch what they do in the tab:

```python
# /// script
# dependencies = ["playwright"]
# ///
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.connect_over_cdp("http://localhost:9222")
    context = browser.contexts[0]  # the browser's own profile
    page = context.new_page()
    page.goto("https://news.ycombinator.com/")
    print(page.locator(".titleline > a").first.inner_text())
```

Save it as `top.py` and run `uv run top.py`: a new tab opens in the
browser and the script prints Hacker News' top story. `uv` installs
Playwright the first time; it doesn't need to download a browser. In
Puppeteer, `puppeteer.connect({ browserURL: "http://localhost:9222" })`
does the same. On a CDP connection, `browser.close()` only disconnects:
the browser keeps running, and so do the tabs the script opened, so
close those with `page.close()` if you don't want them piling up.

From your own computer, an SSH tunnel makes the VM's CDP port local:

```bash
ssh -N -L 9222:localhost:9222 my-browser.exe.xyz
```

While it runs, `http://localhost:9222` on your computer is the VM's
browser. Programs elsewhere, such as a CI job, can also go through
exe.dev's proxy at `https://my-browser.exe.xyz:9222/` with a
[VM token](/docs/https-tokens-for-vms) (`ssh exe.dev ssh-key
generate-api-key --vm=my-browser --label=cdp`) in an
`X-Exedev-Authorization: Bearer ...` header:

```python
browser = p.chromium.connect_over_cdp(
    "https://my-browser.exe.xyz:9222/",
    headers={"X-Exedev-Authorization": "Bearer " + token},
)
```

The nginx in front of Chrome rewrites the WebSocket addresses Chrome
hands out, so they point back through the proxy. Use the `https://`
address; Playwright finds the WebSocket from there.

## Let an agent drive it

The setup runs Playwright MCP next to the browser, on `localhost:8931`.
It gives an agent tools such as `browser_navigate`, `browser_click` and
`browser_snapshot`, which reads the page's structure rather than
screenshots. Connect Claude Code on the VM to the LLM integration (if
you haven't) and to Playwright MCP:

```bash
exeuntu configure claude
claude mcp add --transport http --scope user playwright http://localhost:8931/mcp
claude -p "Use the playwright tools to open https://news.ycombinator.com and tell me the title of the top story." --allowedTools=mcp__playwright
```

With the tab open, you watch the browser go to Hacker News, and Claude
answers with the top story in about ten seconds. For Codex,
`codex mcp add playwright --url http://localhost:8931/mcp`. The model
comes through exe.dev's [LLM integration](/docs/integrations-llm), so
there's no API key on the VM.

[Shelley](/docs/shelley/intro), the agent that comes with every VM, can write
and run Playwright scripts like the one above against `localhost:9222`;
ask it to use that browser so you can watch. An agent somewhere else,
with the [exe MCP](/deploy/exe-mcp), can run them over SSH.

The agent uses the browser you're signed in to. Only sign in to accounts
you'd hand to the agent: a web page can carry instructions that steer
it. For a browser the agent has to itself, use a second VM.

## Headless and light: Lightpanda

For scraping and tests that nobody watches, a full Chrome is more than
you need. [Lightpanda](https://lightpanda.io) is a headless browser
written from scratch for automation: it speaks CDP, runs JavaScript, and
draws nothing. On the VM, next to Chrome or on its own:

```bash
docker run -d --name lightpanda --restart unless-stopped \
  -p 127.0.0.1:9333:9222 -e LIGHTPANDA_DISABLE_TELEMETRY=true \
  lightpanda/browser:latest
```

Then `p.chromium.connect_over_cdp("ws://127.0.0.1:9333")` and
`browser.new_context().new_page()` in the script above. It loaded Hacker
News and read the top story in 0.1 seconds, using 4 MB of memory. It
sends usage telemetry unless `LIGHTPANDA_DISABLE_TELEMETRY` is set, and
not every site works in it yet; when one doesn't, use Chrome.

## What doesn't work

- **No GPU.** The VM has none, so the stream is encoded on the CPU and
  WebGL is off (see the FAQ). Video calls and games are poor.
- **Some streaming video.** YouTube works; services that require
  hardware DRM may not.
- **Sites that block data centers.** Some sites show more CAPTCHAs or
  refuse to sign in from a cloud IP address. That's the address the
  browser has; there's no proxy or residential IP here.
- **One user at a time.** Two people in the tab share the mouse.

<details class="dp-details">
<summary>Back up and restore</summary>

All the state is in `~/browser/config`. To copy the whole VM, on your
computer:

```bash
ssh exe.dev cp my-browser my-browser-backup
```

The copy starts with the browser and its profile, signed in, at
`https://my-browser-backup.exe.xyz:3000/`. To keep just the profile, on
the VM:

```bash
cd ~/browser && docker compose stop
tar -czf ~/browser-config.tgz config
docker compose start
```

Restore by stopping the containers, unpacking it into `~/browser`, and
starting them again.

</details>

<details class="dp-details">
<summary>Update</summary>

LinuxServer.io rebuilds its images when Chrome updates. On the VM:

```bash
cd ~/browser && docker compose pull && docker compose up -d
```

The browser closes cleanly, restarts with the new version, and keeps its
profile. Running the setup script again does the same and also updates
`compose.yaml`.

</details>

<details class="dp-details">
<summary>Troubleshooting</summary>

- **The tab shows *502* or doesn't load.** Check the containers with
  `cd ~/browser && docker compose ps`; `docker logs browser` says why one
  stopped. After a reboot, the browser takes about half a minute.
- **`curl localhost:9222/json/version` fails but the tab works.** The
  `cdp` container shares the browser's network; if you restarted only
  the browser container, run `docker compose up -d` in `~/browser` to
  bring both back.
- **Signed out after a restart.** Chrome writes cookies to disk every 30
  seconds or so. `docker compose restart` and updates close it cleanly
  first, but if the VM restarts right after you sign in, sign in again.
- **The browser says its profile is in use by another computer.** Run
  `docker compose restart` in `~/browser`; the start script clears the
  stale lock.
- **`claude mcp list` says Failed to connect.** Check
  `docker logs browser-mcp-1`. If a browser tool fails after the browser
  restarted, ask the agent to try again.

</details>

## Frequently asked questions

### How is this different from the Linux desktop and computer use recipes?

It's only a browser. [Linux desktop](/deploy/linux-desktop) gives you a whole Xfce desktop with a terminal, an editor and coding agents; [Computer use](/deploy/computer-use) adds a desktop that agents drive from screenshots, for apps that aren't web pages. Here the browser fills the tab, it runs in one container you can update or remove with one command, and it has sound. If all you or your agent need is the web, start here.

### Who can use the browser?

People you've given access to the VM. Ports 3000 and 9222 go through exe.dev's [HTTPS proxy](/docs/proxy), which asks for an exe.dev login or an exe.dev token, and ports other than the main one can never be made public. Remember that whoever can open the tab is signed in to everything you signed in to there.

### Can I use Firefox?

Not with this setup. LinuxServer.io has a Firefox image (`lscr.io/linuxserver/firefox`) that works the same way in a tab, but Firefox no longer speaks Chrome's DevTools protocol, so Playwright's `connect_over_cdp`, Puppeteer and Playwright MCP can't attach to it. `BROWSER=edge` and `BROWSER=chromium` are the choices the setup offers.

### Does video work?

Mostly. YouTube plays with sound, and Widevine-protected test streams play, but streaming services may refuse HD or play nothing, since the VM has no GPU and no hardware DRM. Sites that need WebGL show an error: Chrome has no GPU to draw with. Adding `--enable-unsafe-swiftshader` to `CHROME_CLI` in `~/browser/compose.yaml` turns on software WebGL, which is slow.

### Can I run several browsers?

One VM runs one browser here, with one profile. For another one, with its own logins, create another VM and run the setup there, or copy this one with `ssh exe.dev cp my-browser my-browser-2`. Inside the browser, Chrome's own profiles (the person icon) also work.

### How much does it cost?

The browser shares your exe.dev plan's CPU and memory. On a default VM (2 CPUs, 8 GB), it uses about 400 MB of memory and a few percent of CPU when idle, and about a third of the CPU while you watch a video. The images take about 6 GB of disk.


## Related

- [A desktop and browser for your AI agent](https://exe-staging.dev/deploy/computer-use.md): A Linux desktop your agent drives and you can watch, from any browser.
- [A Linux desktop in your browser](https://exe-staging.dev/deploy/linux-desktop.md): Xfce, Chrome, and coding agents on a VM you open in a browser tab.
- [Run Claude Code on a remote server](https://exe-staging.dev/deploy/claude-code.md): A computer for Claude. A breather for your laptop.
- [Give any agent a sandbox over MCP](https://exe-staging.dev/deploy/exe-mcp.md): One URL gives your agent real VMs to run code on, host apps from, and hand work to.
- [Create sandboxes from Python](https://exe-staging.dev/deploy/python-sandboxes.md): Full VMs as sandboxes. A with block creates one, runs your code, and deletes it.
- [Host OpenClaw](https://exe-staging.dev/deploy/openclaw.md): An AI sidekick with its own computer. Your laptop can take a nap.
