Skip to content

Exposing Ports

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

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

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

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
}

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

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)

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}`);
}

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()

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

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}`);

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);

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;
}

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}`);
}

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() } }
);

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

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

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