Expose services
This guide shows you how to expose services running in your sandbox to the internet via preview URLs.
Expose ports when you need to:
- Test web applications - Preview frontend or backend apps
- Share demos - Give others access to running applications
- Develop APIs - Test endpoints from external tools
- Debug services - Access internal services for troubleshooting
- Build dev environments - Create shareable development workspaces
The typical workflow is: start service → wait for ready → expose port → handle requests with proxyToSandbox
.
import { getSandbox, proxyToSandbox } from "@cloudflare/sandbox";
const sandbox = getSandbox(env.Sandbox, "my-sandbox");
// 1. Start a web serverawait sandbox.startProcess("python -m http.server 8000");
// 2. Wait for service to startawait new Promise((resolve) => setTimeout(resolve, 2000));
// 3. Expose the portconst exposed = await sandbox.exposePort(8000);
// 4. Preview URL is now available (public by default)console.log("Server accessible at:", exposed.exposedAt);// Returns: https://abc123-8000.sandbox.workers.dev
// 5. Handle preview URL requests in your Workerexport default { async fetch(request, env) { // Proxy requests to the exposed port return proxyToSandbox(request, env.Sandbox, "my-sandbox"); },};
import { getSandbox, proxyToSandbox } from '@cloudflare/sandbox';
const sandbox = getSandbox(env.Sandbox, 'my-sandbox');
// 1. Start a web serverawait sandbox.startProcess('python -m http.server 8000');
// 2. Wait for service to startawait new Promise(resolve => setTimeout(resolve, 2000));
// 3. Expose the portconst exposed = await sandbox.exposePort(8000);
// 4. Preview URL is now available (public by default)console.log('Server accessible at:', exposed.exposedAt);// Returns: https://abc123-8000.sandbox.workers.dev
// 5. Handle preview URL requests in your Workerexport default { async fetch(request: Request, env: Env): Promise<Response> { // Proxy requests to the exposed port return proxyToSandbox(request, env.Sandbox, 'my-sandbox'); }};
When exposing multiple ports, use names to stay organized:
// Start and expose API serverawait sandbox.startProcess("node api.js", { env: { PORT: "8080" } });await new Promise((resolve) => setTimeout(resolve, 2000));const api = await sandbox.exposePort(8080, { name: "api" });
// Start and expose frontendawait sandbox.startProcess("npm run dev", { env: { PORT: "5173" } });await new Promise((resolve) => setTimeout(resolve, 2000));const frontend = await sandbox.exposePort(5173, { name: "frontend" });
console.log("Services:");console.log("- API:", api.exposedAt);console.log("- Frontend:", frontend.exposedAt);
// Start and expose API serverawait sandbox.startProcess('node api.js', { env: { PORT: '8080' } });await new Promise(resolve => setTimeout(resolve, 2000));const api = await sandbox.exposePort(8080, { name: 'api' });
// Start and expose frontendawait sandbox.startProcess('npm run dev', { env: { PORT: '5173' } });await new Promise(resolve => setTimeout(resolve, 2000));const frontend = await sandbox.exposePort(5173, { name: 'frontend' });
console.log('Services:');console.log('- API:', api.exposedAt);console.log('- Frontend:', frontend.exposedAt);
Always verify a service is ready before exposing. Use a simple delay for most cases:
// Start serviceawait sandbox.startProcess("npm run dev", { env: { PORT: "8080" } });
// Wait 2-3 secondsawait new Promise((resolve) => setTimeout(resolve, 2000));
// Now exposeawait sandbox.exposePort(8080);
// Start serviceawait sandbox.startProcess('npm run dev', { env: { PORT: '8080' } });
// Wait 2-3 secondsawait new Promise(resolve => setTimeout(resolve, 2000));
// Now exposeawait sandbox.exposePort(8080);
For critical services, poll the health endpoint:
await sandbox.startProcess("node api-server.js", { env: { PORT: "8080" } });
// Wait for health checkfor (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);
await sandbox.startProcess('node api-server.js', { env: { PORT: '8080' } });
// Wait for health checkfor (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);
Expose multiple ports for full-stack applications:
// Start backendawait sandbox.startProcess("node api/server.js", { env: { PORT: "8080" },});await new Promise((resolve) => setTimeout(resolve, 2000));
// Start frontendawait sandbox.startProcess("npm run dev", { cwd: "/workspace/frontend", env: { PORT: "5173", API_URL: "http://localhost:8080" },});await new Promise((resolve) => setTimeout(resolve, 3000));
// Expose bothconst api = await sandbox.exposePort(8080, { name: "api" });const frontend = await sandbox.exposePort(5173, { name: "frontend" });
return Response.json({ api: api.exposedAt, frontend: frontend.exposedAt,});
// Start backendawait sandbox.startProcess('node api/server.js', { env: { PORT: '8080' }});await new Promise(resolve => setTimeout(resolve, 2000));
// Start frontendawait sandbox.startProcess('npm run dev', { cwd: '/workspace/frontend', env: { PORT: '5173', API_URL: 'http://localhost:8080' }});await new Promise(resolve => setTimeout(resolve, 3000));
// Expose bothconst api = await sandbox.exposePort(8080, { name: 'api' });const frontend = await sandbox.exposePort(5173, { name: 'frontend' });
return Response.json({ api: api.exposedAt, frontend: frontend.exposedAt});
const { ports, count } = await sandbox.getExposedPorts();
console.log(`${count} ports currently exposed:`);
for (const port of ports) { console.log(` Port ${port.port}: ${port.exposedAt}`); 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.exposedAt}`); if (port.name) { console.log(` Name: ${port.name}`); }}
// Unexpose a single portawait sandbox.unexposePort(8000);
// Unexpose multiple portsfor (const port of [3000, 5173, 8080]) { await sandbox.unexposePort(port);}
// Unexpose a single portawait sandbox.unexposePort(8000);
// Unexpose multiple portsfor (const port of [3000, 5173, 8080]) { await sandbox.unexposePort(port);}
- 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
When developing locally with wrangler dev
, you must expose ports in your Dockerfile:
FROM docker.io/cloudflare/sandbox:0.3.3
# Expose ports you plan to useEXPOSE 8000EXPOSE 8080EXPOSE 5173
Update wrangler.jsonc
to use your Dockerfile:
{ "containers": [ { "class_name": "Sandbox", "image": "./Dockerfile" } ]}
In production, all ports are available and controlled programmatically via exposePort()
/ unexposePort()
.
Port 3000 is used by the internal Bun server and cannot be exposed:
// ❌ This will failawait sandbox.exposePort(3000); // Error: Port 3000 is reserved
// ✅ Use a different portawait sandbox.startProcess("node server.js", { env: { PORT: "8080" } });await sandbox.exposePort(8080);
// ❌ This will failawait sandbox.exposePort(3000); // Error: Port 3000 is reserved
// ✅ Use a different portawait sandbox.startProcess('node server.js', { env: { PORT: '8080' } });await sandbox.exposePort(8080);
Wait for the service to start before exposing:
await sandbox.startProcess("npm run dev");await new Promise((resolve) => setTimeout(resolve, 3000));await sandbox.exposePort(8080);
await sandbox.startProcess('npm run dev');await new Promise(resolve => setTimeout(resolve, 3000));await sandbox.exposePort(8080);
Check before exposing to avoid errors:
const { ports } = await sandbox.getExposedPorts();if (!ports.some((p) => p.port === 8080)) { await sandbox.exposePort(8080);}
const { ports } = await sandbox.getExposedPorts();if (!ports.some(p => p.port === 8080)) { await sandbox.exposePort(8080);}
Preview URLs follow the pattern https://{sandbox-id}-{port}.sandbox.workers.dev
:
- Port 8080:
https://abc123-8080.sandbox.workers.dev
- Port 5173:
https://abc123-5173.sandbox.workers.dev
Note: Port 3000 is reserved for the internal Bun server and cannot be exposed.
- Ports API reference - Complete port exposure API
- Background processes guide - Managing services
- Execute commands guide - Starting services
Was this helpful?
- Resources
- API
- New to Cloudflare?
- Directory
- Sponsorships
- Open Source
- Support
- Help Center
- System Status
- Compliance
- GDPR
- Company
- cloudflare.com
- Our team
- Careers
- © 2025 Cloudflare, Inc.
- Privacy Policy
- Terms of Use
- Report Security Issues
- Trademark
-