Expose services·续

// Extract hostname from request
const { hostname } = new URL(request.url);

await sandbox.startProcess('node api-server.js', { env: { PORT: '8080' } });

// Wait for health check
for (let i = 0; i < 10; i++) {
  await new Promise(resolve => setTimeout(resolve, 1000));

const check = await sandbox.exec('curl -f http://localhost:8080/health || echo "not ready"');
  if (check.stdout.includes('ok')) {
    break;
  }
}

await sandbox.exposePort(8080, { hostname });

Multiple services

Expose multiple ports for full-stack applications:

// Extract hostname from request
const { hostname } = new URL(request.url);

// Start backend
await sandbox.startProcess("node api/server.js", {
    env: { PORT: "8080" },
});
await new Promise((resolve) => setTimeout(resolve, 2000));

// Start frontend
await sandbox.startProcess("npm run dev", {
    cwd: "/workspace/frontend",
    env: { PORT: "5173", API_URL: "http://localhost:8080" },
});
await new Promise((resolve) => setTimeout(resolve, 3000));

// Expose both
const api = await sandbox.exposePort(8080, { hostname, name: "api" });
const frontend = await sandbox.exposePort(5173, { hostname, name: "frontend" });

return Response.json({
    api: api.url,
    frontend: frontend.url,
});
// Extract hostname from request
const { hostname } = new URL(request.url);

// Start backend
await sandbox.startProcess('node api/server.js', {
  env: { PORT: '8080' }
});
await new Promise(resolve => setTimeout(resolve, 2000));

// Start frontend
await sandbox.startProcess('npm run dev', {
  cwd: '/workspace/frontend',
  env: { PORT: '5173', API_URL: 'http://localhost:8080' }
});
await new Promise(resolve => setTimeout(resolve, 3000));

// Expose both
const api = await sandbox.exposePort(8080, { hostname, name: 'api' });
const frontend = await sandbox.exposePort(5173, { hostname, name: 'frontend' });

return Response.json({
  api: api.url,
  frontend: frontend.url
});

Manage exposed ports

List currently exposed ports

const { ports, count } = await sandbox.getExposedPorts();

console.log(`${count} ports currently exposed:`);

for (const port of ports) {
    console.log(`  Port ${port.port}: ${port.url}`);
    if (port.name) {
        console.log(`    Name: ${port.name}`);
    }
}
const { ports, count } = await sandbox.getExposedPorts();

console.log(`${count} ports currently exposed:`);

for (const port of ports) {
  console.log(`  Port ${port.port}: ${port.url}`);
  if (port.name) {
    console.log(`    Name: ${port.name}`);
  }
}

Unexpose ports

// Unexpose a single port
await sandbox.unexposePort(8000);

// Unexpose multiple ports
for (const port of [3000, 5173, 8080]) {
    await sandbox.unexposePort(port);
}
// Unexpose a single port
await sandbox.unexposePort(8000);

// Unexpose multiple ports
for (const port of [3000, 5173, 8080]) {
  await sandbox.unexposePort(port);
}

Best practices

  • Wait for readiness - Don't expose ports immediately after starting processes
  • Use named ports - Easier to track when exposing multiple ports
  • Clean up - Unexpose ports when done to prevent abandoned URLs
  • Add authentication - Preview URLs are public; protect sensitive services

Local development

When developing locally with wrangler dev, you must expose ports in your Dockerfile:

Dockerfiledockerfile

FROM docker.io/cloudflare/sandbox:0.12.14

# Expose ports you plan to use
EXPOSE 8000
EXPOSE 8080
EXPOSE 5173

Update wrangler.jsonc to use your Dockerfile:

wrangler.jsoncjsonc

{
  "containers": [
    {
      "class_name": "Sandbox",
      "image": "./Dockerfile"
    }
  ]
}

In production, all ports are available and controlled programmatically via exposePort() / unexposePort().

Troubleshooting

Port 3000 is reserved

Port 3000 is used by the internal Bun server and cannot be exposed:

// Extract hostname from request
const { hostname } = new URL(request.url);

// ❌ This will fail
await sandbox.exposePort(3000, { hostname }); // Error: Port 3000 is reserved

// ✅ Use a different port
await sandbox.startProcess("node server.js", { env: { PORT: "8080" } });
await sandbox.exposePort(8080, { hostname });
// Extract hostname from request
const { hostname } = new URL(request.url);

// ❌ This will fail
await sandbox.exposePort(3000, { hostname });  // Error: Port 3000 is reserved

// ✅ Use a different port
await sandbox.startProcess('node server.js', { env: { PORT: '8080' } });
await sandbox.exposePort(8080, { hostname });

Port not ready

Wait for the service to start before exposing:

// Extract hostname from request
const { hostname } = new URL(request.url);

await sandbox.startProcess("npm run dev");
await new Promise((resolve) => setTimeout(resolve, 3000));
await sandbox.exposePort(8080, { hostname });
// Extract hostname from request
const { hostname } = new URL(request.url);

await sandbox.startProcess('npm run dev');
await new Promise(resolve => setTimeout(resolve, 3000));
await sandbox.exposePort(8080, { hostname });

Port already exposed

Check before exposing to avoid errors:

// Extract hostname from request
const { hostname } = new URL(request.url);

const { ports } = await sandbox.getExposedPorts();
if (!ports.some((p) => p.port === 8080)) {
    await sandbox.exposePort(8080, { hostname });
}
// Extract hostname from request
const { hostname } = new URL(request.url);

const { ports } = await sandbox.getExposedPorts();
if (!ports.some(p => p.port === 8080)) {
  await sandbox.exposePort(8080, { hostname });
}

Uppercase sandbox ID error

Error: Preview URLs require lowercase sandbox IDs

Cause: You created a sandbox with uppercase characters (e.g., "MyProject-123") but preview URLs always use lowercase in routing, causing a mismatch.

Solution:

// Create sandbox with normalization
const sandbox = getSandbox(env.Sandbox, "MyProject-123", { normalizeId: true });
await sandbox.exposePort(8080, { hostname });
// Create sandbox with normalization
const sandbox = getSandbox(env.Sandbox, 'MyProject-123', { normalizeId: true });
await sandbox.exposePort(8080, { hostname });

This creates the Durable Object with ID "myproject-123", matching the preview URL routing.

See Sandbox options - normalizeId for details.

Preview URL Format

Production: https://{port}-{sandbox-id}-{token}.yourdomain.com

  • Auto-generated token: https://8080-abc123-random16chars12.yourdomain.com
  • Custom token: https://8080-abc123-my-api-v1.yourdomain.com

Local development: http://localhost:8787/...

Note: Port 3000 is reserved for the internal Bun server and cannot be exposed.

Was this helpful?

YesNo

On this page

Expose services·续 - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/sandbox/sdk/guides/expose-services/#page","headline":"Expose services","description":"Create preview URLs and expose ports for web services with the Sandbox SDK 0.x.","url":"https://developers.cloudflare.com/sandbox/sdk/guides/expose-services/","inLanguage":"en","image":"https://developers.cloudflare.com/sandbox/sdk/guides/expose-services/og.png?v=be6c05fa066c6b60","dateModified":"2026-10-23","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}


description: Monitor files and directories in real time with the Sandbox SDK 0.x to build development tools and automation workflows. title: Watch filesystem changes image: https://developers.cloudflare.com/sandbox/sdk/guides/file-watching/og.png?v=d3741a73df2e386a


Skip to content

Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/sandbox/llms.txt
Use this file to discover all available pages before exploring further.