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.
Related resources
- Ports API reference - Complete port exposure API
- Background processes guide - Managing services
- Execute commands guide - Starting services
- Tunnels API reference - Recommended alternative for most public-URL use cases (quick or named tunnels)
Was this helpful?
YesNo
On this page
{"@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
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.
Docs