# How do I preview the dev server my agent is running?

> Allow the dev server's port on the Computer's template, then run sanbox ports forward to open it on your own machine. HTTP and SSE work; WebSockets, and so hot reload, don't.

- Source: Sanbox knowledge base, by Sanlabs GmbH
- URL: https://sanbox.cloud/kb/parallel-work/preview-your-agents-dev-server/
- Type: how-to
- Section: Parallel and background work
- Language: en
- Published: 2026-11-06
- Updated: 2026-11-06

Forward the port to your own machine, as you would with an SSH tunnel to a VPS. In Sanbox, an admin allows the port on the Computer's template, and `sanbox ports forward <run-id> 3000:5173` serves the agent's dev server at `http://127.0.0.1:3000` on your laptop, with nothing made public. Page loads, API calls and server-sent events (SSE) work; WebSockets don't, so hot reload won't connect and you refresh the page to see changes.

## Run this with your agent

```text
Read https://sanbox.cloud/kb/parallel-work/preview-your-agents-dev-server.md and follow it to preview my agent's dev server.
If the Sanbox CLI is missing, install it with `npm install -g @sanlabs/sanbox-cli@latest`
and sign in with `sanbox login`. Show me each command before you create, stop or delete a Computer.
```

## How does port access work?

Port access is off by default. A template's inbound rules list the ports that authenticated port connections may reach, and optionally which public IPs or CIDRs may call. Nothing becomes public: every caller needs a short-lived token, or goes through the CLI's local forwarder, which handles the token for you.

It works for an OpenCode Computer where the agent runs `npm run dev`, and for a container in a Docker Computer. Port access needs CLI 0.0.20 or later.

## Steps

### 1. Allow the port on the template

An organisation admin adds the port when creating the template:

```bash
sanbox templates create --name "Web dev" --type opencode-computer \
  --model-provider <provider-id> --model <model-id> \
  --network-policy policy.json --inbound-port 5173 --json
```

Or on an existing template, replacing its rules:

```bash
sanbox templates inbound set <template-id> --port 5173 --json
```

Add `--source <ip-or-cidr>` to accept only listed public addresses. Each run keeps the rules it was created with, so a template change applies only to new Computers you start from the template afterwards, not to existing ones you resume. A few ports are reserved for Sanbox, and the CLI rejects them.

### 2. Start a Computer

```bash
sanbox run --template <template-id> --json
```

### 3. Start the dev server inside it

The server must listen on the allowed port, on `127.0.0.1` or `0.0.0.0`. Ask the agent to start it, or run it yourself in the console terminal, where the session keeps running after you close the tab. From your machine:

```bash
sanbox exec <run-id> --cwd /workspace/app --detach --timeout 86400 \
  --request-key <key> --json -- 'npm run dev -- --host 127.0.0.1 --port 5173'
```

That example passes Vite's `--host` and `--port` options; use your dev server's equivalents. Commands stop at their timeout, which is 3,600 seconds unless you raise it, so pass `--timeout` for a long-lived server. In a Docker Computer, publish the container's port on loopback:

```bash
sanbox exec <run-id> -- docker run -d -p 127.0.0.1:8080:8080 <image>
```

### 4. Forward it to your machine

```bash
sanbox ports forward <run-id> 3000:5173
```

Open `http://127.0.0.1:3000`. The local port comes first, like `ssh -L`. The forwarder creates its own token, renews it before it expires, and revokes it when you press Ctrl-C. It listens only on `127.0.0.1`, `::1` or `localhost`, and refuses requests from web pages on other sites. Keep it running while you use the preview.

### 5. Call it from a server instead

For a backend, a test runner or a teammate's service, create a connection and use its URL and token:

```bash
sanbox ports connect <run-id> --port 5173 --expires 1h --json
curl -N -H "Authorization: Bearer $PORT_TOKEN" "$PORT_URL/api/health"
```

The response holds `data.url` and a one-time `data.access_token`. Treat the token as a secret. It works only for that run, those ports and the current boot, and expires after 1 minute to 1 day (1 hour by default).

## Why doesn't hot reload work?

Port connections carry HTTP requests and streamed responses such as SSE. They don't carry WebSockets, HTTP/2, gRPC or raw TCP. Vite, for example, uses a WebSocket for hot module replacement, so the page loads but live updates don't arrive.

- **Refresh by hand** after the agent changes code.
- **Turn hot reload off** if the failed connection gets in the way. In Vite, `server.hmr: false` disables it.

## What else should I know?

- **A `ports connect` URL is an API proxy, not a preview site.** Sanbox strips the URL prefix, so the service receives `/api/health` for `$PORT_URL/api/health`, and pages with absolute links break. Use `ports forward` for pages.
- **A browser `EventSource` can't send the token.** Call connection URLs from a server.
- **Headers within 30 seconds.** The service must start its response in time. The body can then stream while the token works.
- **Every request appears to come from `127.0.0.1`.** Don't let the dev server treat loopback callers as trusted.
- **Sanbox removes `Authorization` and `Cookie` headers** before requests reach the service, and drops `Set-Cookie` from responses, so cookie-based logins in the app won't stick.
- **Resume ends old connections.** Old tokens return `410 port_connection_ended`. Forward again after a resume. A fork needs its own connection.

## Common errors

| Error | What it means |
|---|---|
| `409 port_access_disabled` | The run allows no ports. Allow the port on the template, then start a new Computer. |
| `400 port_not_allowed` | The port isn't in the run's inbound rules. |
| `403 port_connection_source_denied` | No rule admits the caller's address. The message names the address Sanbox saw. |
| `502 port_unreachable` | Nothing listens on that port. Check the server is running on `127.0.0.1` or `0.0.0.0`. |
| `409 port_access_unsupported` | The run's image predates port access. Start a new Computer from the template. |
| `429 port_connection_busy` | The Computer already has 64 open requests through Sanbox. |

## Snapshot it

Computers started from a snapshot or warm pool keep the ports of the Computer the snapshot came from. If the snapshot predates your inbound rule, start a Computer from the updated template, prepare it, and take a new snapshot.

## What this doesn't do

- **No public URL.** Every caller needs a token or the local forwarder.
- **No WebSockets, HTTP/2, gRPC or raw TCP.**
- **No changes to existing Computers.** New inbound rules apply only to new Computers started from the template after you save them.

## Related

- [How do I run Docker Compose inside an AI agent's sandbox?](https://sanbox.cloud/kb/parallel-work/docker-compose-inside-an-agent-computer/)
- [How do I run OpenCode as a server on a remote computer?](https://sanbox.cloud/kb/coding-agents/opencode-server-on-a-remote-computer/)
- [How do I restrict which domains an AI agent can reach?](https://sanbox.cloud/kb/security/restrict-which-domains-an-agent-can-reach/)

## Sources

- Vite, [Server options: server.hmr and server.ws](https://vite.dev/config/server-options), checked 2026-11-06
- Vite, [Command line interface: --host and --port](https://vite.dev/guide/cli), checked 2026-11-06

## Next step

Talk to us at sales@sanlabs.ai. Sanlabs' forward-deployed engineers build agent use cases with your team, with experience in aviation, pharmaceuticals, supply chain and logistics, and internal admin workflows. How we work: https://sanbox.cloud/kb/teams/forward-deployed-engineering.md

Existing customers can use Sanbox from a coding agent: install the CLI with `npm install -g @sanlabs/sanbox-cli@latest`, sign in with `sanbox login`, then run `sanbox skill` for the full agent guide.
