# How Sandbox Port Exposure and Domain Mapping Work in Open Agents

> Learn how Open Agents maps sandbox port exposure to unique public subdomains. Discover routable URLs injected as environment variables for effortless web service exposure.

- Repository: [Vercel Labs/open-agents](https://github.com/vercel-labs/open-agents)
- Tags: deep-dive
- Published: 2026-04-16

---

**Open Agents automatically maps TCP ports declared inside a Vercel Sandbox to unique public sub-domains, injecting routable URLs as environment variables so user processes can expose web services without hard-coding endpoints.**

The `vercel-labs/open-agents` repository runs user code inside isolated Firecracker MicroVMs called Vercel Sandboxes. When you need to expose a development server or API from within these sandboxes, the platform handles port exposure and domain mapping through a declarative configuration system implemented in [`packages/sandbox/vercel/sandbox.ts`](https://github.com/vercel-labs/open-agents/blob/main/packages/sandbox/vercel/sandbox.ts).

## Declaring Ports at Sandbox Creation

You specify which ports your application will listen on by passing a `ports` array when initializing the sandbox connection. These ports are stored in the private field `this._ports` and represent the explicit TCP endpoints you intend to expose.

```typescript
import { connectVercelSandbox } from "packages/sandbox/vercel";

const sandbox = await connectVercelSandbox({
  name: "demo-session",
  ports: [3000, 5173],  // These become routable preview ports
});

```

## The Domain Resolution Pipeline

The mapping logic follows a multi-step resolution process that combines declared ports with dynamically discovered routes, then resolves the first available public host.

### Collecting Declared and Dynamic Ports

The system first aggregates all potential ports using `getRoutePorts()` and `getPreviewPorts()`. The `getRoutePorts()` method extracts ports from `sdk.routes` (lines 35-44), while `getPreviewPorts()` merges these with the initially declared ports stored in `this._ports` (lines 46-49).

```typescript
// Logic from packages/sandbox/vercel/sandbox.ts
getPreviewPorts(): number[] {
  return [...this._ports, ...this.getRoutePorts()];
}

```

### Building the Candidate Port Set

The `getCandidatePorts()` method creates a deduplicated list of all preview ports plus the implicit HTTP port `80` as a fallback mechanism (lines 52-54). This ensures that even if specific application ports fail to resolve, the sandbox can still attempt to route through the standard HTTP port.

### Resolving the Public Host

The `host` getter implements the core resolution logic (lines 69-88). It iterates over the candidate ports and queries the Vercel SDK for a valid domain via `sdk.domain(port)`. The first successful lookup becomes the canonical host. If all specific ports fail, the system attempts port `80` as a final fallback.

Once resolved, the `domain(port)` convenience wrapper exposes full URLs by forwarding to the SDK's `session.domain(port)` implementation (lines 1014-1015), returning strings like `https://sbx-3000-abc123.vercel.run`.

## Runtime Environment Injection

To eliminate hard-coded URLs in user code, the sandbox injects connectivity information through environment variables generated by `getRuntimePreviewEnv()` (lines 56-62). Every command executed inside the sandbox receives:

- **`SANDBOX_HOST`** — The canonical host derived from the first successful port resolution (e.g., `sbx-abc123.vercel.run`)
- **`SANDBOX_URL_<PORT>`** — Full HTTPS URL for each routable port (e.g., `SANDBOX_URL_3000=https://sbx-3000-abc123.vercel.run`)

These variables are merged with any user-provided `env` configuration before command execution.

## Practical Implementation Examples

### Creating a Sandbox and Accessing URLs

This example demonstrates declaring ports and retrieving both the host and specific port URLs:

```typescript
const sandbox = await connectVercelSandbox({
  name: "demo-session",
  ports: [3000, 5173],
});

console.log("Sandbox host:", sandbox.host);
// → sbx-abc123.vercel.run

console.log("Port 3000 URL:", sandbox.domain(3000));
// → https://sbx-3000-abc123.vercel.run

```

### Using Environment Variables Inside Commands

Reference the injected URLs without knowing the actual sub-domain at build time:

```typescript
await sandbox.exec(
  "echo $SANDBOX_HOST && echo $SANDBOX_URL_3000",
  "/vercel/sandbox",
  15_000,
);
// Output: sbx-abc123.vercel.run
//         https://sbx-3000-abc123.vercel.run

```

### Handling Dynamic Port Discovery

If an application starts listening on a random port after sandbox initialization, the Vercel SDK adds the route to `sdk.routes`. The sandbox automatically includes these via `getPreviewPorts()`:

```typescript
// After the app has started...
const newPort = sandbox.getPreviewPorts().find(p => p !== 80);
console.log("Discovered port URL:", sandbox.domain(newPort!));

```

## Summary

- **Port declaration** occurs at sandbox creation via the `ports` array in `connectVercelSandbox()`, stored internally as `this._ports`.
- **Domain resolution** aggregates declared ports and dynamic routes, deduplicates them with port `80` fallback, and resolves the first available public host using the `host` getter (lines 69-88).
- **URL generation** happens through `domain(port)` (lines 1014-1015), returning complete HTTPS URLs mapped to unique Vercel sub-domains.
- **Runtime exposure** uses `getRuntimePreviewEnv()` (lines 56-62) to inject `SANDBOX_HOST` and `SANDBOX_URL_<PORT>` variables into every sandbox process.
- **Fallback behavior** ensures that if no ports are declared or routable, the system attempts to resolve port `80` to maintain connectivity.

## Frequently Asked Questions

### What happens if no ports are declared when creating a sandbox?

If you omit the `ports` array or none of the declared ports are currently routable, the domain resolution logic falls back to port `80`. The `getCandidatePorts()` method (lines 52-54) always includes port `80` in the candidate set, and the `host` getter (lines 69-88) attempts this standard HTTP port as a final fallback before failing.

### How does dynamic port discovery work for applications that bind to random ports?

When an application starts listening on a port not declared at initialization, the Vercel SDK reports this new route via `sdk.routes`. The `getRoutePorts()` method (lines 35-44) extracts these dynamic ports, and `getPreviewPorts()` (lines 46-49) merges them with the originally declared ports. This allows `domain(port)` to resolve URLs for runtime-allocated ports without restarting the sandbox.

### How can processes inside the sandbox determine their public URLs?

Each command receives automatically generated environment variables built by `getRuntimePreviewEnv()` (lines 56-62). The `SANDBOX_HOST` variable contains the canonical host, while `SANDBOX_URL_<PORT>` variables provide the full HTTPS URL for each exposed port. User code can reference these variables instead of hard-coding domain names.

### Why does the system include port 80 as a fallback candidate?

Port `80` serves as the default HTTP entry point provided by the Vercel infrastructure. By including it in the candidate set via `getCandidatePorts()` (lines 52-54), the sandbox ensures that a host is always resolvable when possible, even if application-specific ports are temporarily unavailable or were never declared. This provides a baseline connectivity option for health checks or basic HTTP responses.