Docs / guides / ports

Exposing Ports

How to expose ports in Runra Sandbox — exposePort(), URL generation, port ranges, security, and use cases for dev servers, previews, and browser-based workflows

Exposing ports allows you to access services running inside your sandbox — dev servers, web apps, APIs, databases, and preview environments — from outside the sandbox.

Basic port exposure

Use exposePort() to make a sandbox service publicly accessible:

const sandbox = await runra.sandboxes.create({
  image: "node:22",
  resources: { cpu: 2, memoryMb: 4096 },
});

// Start a dev server inside the sandbox
await sandbox.exec("npx create-react-app my-app");
await sandbox.exec("cd my-app && npm start &");

// Expose port 3000
const port = await sandbox.exposePort(3000);
console.log(port.url);
// https://3000-abc123.box.runra.dev

URL format

Exposed ports follow this URL pattern:

https://{port}-{sandboxId}.{domain}
ComponentExampleDescription
{port}3000The exposed port number
{sandboxId}abc123Your sandbox ID (first 6 chars)
{domain}box.runra.devThe Runra Sandbox domain

Full URL: https://3000-abc123.box.runra.dev

The Port type

interface Port {
  port: number;       // The port number exposed
  url: string;        // Public HTTPS URL to access the port
  sandboxId: string;  // The sandbox this port belongs to
}

Multiple ports

Expose multiple ports for full-stack applications:

// Start frontend and backend
await sandbox.exec("cd frontend && npm run dev &");
await sandbox.exec("cd backend && npm run dev &");

// Expose both
const frontend = await sandbox.exposePort(3000);
const backend = await sandbox.exposePort(8000);
const storybook = await sandbox.exposePort(6006);

console.log(frontend.url);  // https://3000-abc123.box.runra.dev
console.log(backend.url);   // https://8000-abc123.box.runra.dev
console.log(storybook.url); // https://6006-abc123.box.runra.dev

Port ranges

You can expose ports in the range 102465535. Ports below 1024 are reserved for system services.

// Valid port range
const port = await sandbox.exposePort(8080);  // ✅ Valid
const port = await sandbox.exposePort(443);   // ❌ Reserved (below 1024)
const port = await sandbox.exposePort(99999); // ❌ Invalid (above 65535)

Checking exposed ports

List all currently exposed ports in a sandbox:

const ports = await sandbox.getPorts();
for (const port of ports) {
  console.log(`Port ${port.port}: ${port.url}`);
}

Closing ports

Close an exposed port when it's no longer needed:

await sandbox.closePort(3000);
// Port 3000 is no longer publicly accessible

Ports are automatically closed when:

  • The sandbox is paused
  • The sandbox is terminated
  • You explicitly call closePort()

Use cases

Dev server previews

Preview web applications during development:

const sandbox = await runra.sandboxes.create({ image: "node:22" });
await sandbox.exec("git clone https://github.com/acme/webapp.git");
await sandbox.exec("cd webapp && npm install && npm run dev &");

const preview = await sandbox.exposePort(5173);
console.log(`Preview available at: ${preview.url}`);
// Share this URL with your team for instant feedback

Browser-based workflows

Agents can expose browser automation UIs:

const sandbox = await runra.sandboxes.create({ image: "ubuntu:24.04" });

// Start a VNC-based browser for agent use
await sandbox.exec("Xvfb :99 &");
await sandbox.exec("novnc --vnc localhost:5900 &");

const browserUI = await sandbox.exposePort(6080);
console.log(`Browser UI: ${browserUI.url}`);

API endpoints for testing

Expose API endpoints so your CI or test suite can interact with sandboxed services:

// Run a mock API inside the sandbox
await sandbox.exec("cd mock-api && node server.js &");

const api = await sandbox.exposePort(4000);

// External test can now hit the API
const response = await fetch(`${api.url}/users`);
const users = await response.json();
expect(users).toHaveLength(3);

Temporary staging environments

Create on-demand staging environments with exposed ports:

async function createStagingEnv(branch: string): Promise<string> {
  const sandbox = await runra.sandboxes.create({ image: "node:22" });
  await sandbox.exec(`git clone -b ${branch} https://github.com/acme/app.git`);
  await sandbox.exec("cd app && docker-compose up -d");
  
  const app = await sandbox.exposePort(3000);
  const db = await sandbox.exposePort(5432);
  
  console.log(`Staging env for ${branch}:`);
  console.log(`  App: ${app.url}`);
  console.log(`  DB:  ${db.url}`);
  
  return sandbox.id;
}

Multi-service architectures

Run and expose microservices inside a single sandbox:

await sandbox.exec("docker-compose up -d");

// Expose all services
const services = {
  frontend: await sandbox.exposePort(3000),
  api: await sandbox.exposePort(4000),
  admin: await sandbox.exposePort(4001),
  metrics: await sandbox.exposePort(9090),
};

for (const [name, port] of Object.entries(services)) {
  console.log(`${name}: ${port.url}`);
}

Security

Authentication

Exposed ports are public by default. For additional security:

// Use basic auth on the service itself
await sandbox.exec(
  "node server.js --auth-user=admin --auth-pass=${ADMIN_PASS}",
  { env: { ADMIN_PASS: generateToken() } }
);

Access control

Port URLs include the sandbox ID, making them unguessable. However, treat port URLs as sensitive and:

  • Don't share URLs in public channels without auth
  • Close ports immediately when no longer needed
  • Use short sandbox lifetimes for preview environments

Network policy

Control which ports can be exposed:

const sandbox = await runra.sandboxes.create({
  image: "node:22",
  network: {
    ingress: {
      allowPorts: [3000, 5173, 8080], // Only these ports can be exposed
    },
  },
});

await sandbox.exposePort(3000); // ✅ Allowed
await sandbox.exposePort(9000); // ❌ Blocked by network policy

Lifecycle

Ports follow the sandbox lifecycle:

Sandbox statePort status
runningPorts are active and accessible
pausedPorts are closed, URLs stop resolving
resumedPorts are automatically re-exposed with same URLs
terminatedPorts are permanently closed
const sandbox = await runra.sandboxes.create({ image: "node:22" });
await sandbox.exec("python -m http.server 8000 &");
const port = await sandbox.exposePort(8000);

await sandbox.pause();
// port.url is now unreachable

const resumed = await runra.sandboxes.resume(sandbox.id);
// port.url is accessible again with the same URL

Next steps