← Sandbox SDK / sandbox / guides
Публикация сервисов
В этом руководстве показано, как открыть сервисам, работающим в вашей песочнице, доступ из интернета через preview URL.
Когда открывать порты
Открывайте порты, когда нужно:
- Тестирование веб-приложений - Предпросмотр фронтенд- и бэкенд-приложений
- Общий доступ к демонстрациям - Предоставляйте другим доступ к запущенным приложениям
- Разработка API - Тестирование конечных точек с помощью внешних инструментов
- Сервисы отладки - Доступ к внутренним сервисам для диагностики
- Создание сред разработки - Создавайте общие рабочие пространства для разработки
Базовое открытие портов
Типичный порядок работы такой: запустите сервис → дождитесь готовности → откройте порт → обрабатывайте запросы с помощью proxyToSandbox.
import { getSandbox, proxyToSandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";
export default {
async fetch(request, env) {
// Proxy requests to exposed ports first
const proxyResponse = await proxyToSandbox(request, env);
if (proxyResponse) return proxyResponse;
// Extract hostname from request
const { hostname } = new URL(request.url);
const sandbox = getSandbox(env.Sandbox, "my-sandbox");
// 1. Start a web server
await sandbox.startProcess("python -m http.server 8000");
// 2. Wait for service to start
await new Promise((resolve) => setTimeout(resolve, 2000));
// 3. Expose the port
const exposed = await sandbox.exposePort(8000, { hostname });
// 4. Preview URL is now available (public by default)
console.log("Server accessible at:", exposed.url);
// Production: https://8000-abc123.yourdomain.com
// Local dev: http://localhost:8787/...
return Response.json({ url: exposed.url });
},
};import { getSandbox, proxyToSandbox } from '@cloudflare/sandbox';
export { Sandbox } from '@cloudflare/sandbox';
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// Proxy requests to exposed ports first
const proxyResponse = await proxyToSandbox(request, env);
if (proxyResponse) return proxyResponse;
// Extract hostname from request
const { hostname } = new URL(request.url);
const sandbox = getSandbox(env.Sandbox, 'my-sandbox');
// 1. Start a web server
await sandbox.startProcess('python -m http.server 8000');
// 2. Wait for service to start
await new Promise(resolve => setTimeout(resolve, 2000));
// 3. Expose the port
const exposed = await sandbox.exposePort(8000, { hostname });
// 4. Preview URL is now available (public by default)
console.log('Server accessible at:', exposed.url);
// Production: https://8000-abc123.yourdomain.com
// Local dev: http://localhost:8787/...
return Response.json({ url: exposed.url });
}
};Стабильные URL с пользовательскими токенами
Для продакшен-развёртываний или при передаче URL пользователям используйте пользовательские токены, чтобы preview-URL оставались постоянными при перезапусках контейнера:
// Extract hostname from request
const { hostname } = new URL(request.url);
// Without custom token - URL changes on restart
const exposed = await sandbox.exposePort(8080, { hostname });
// https://8080-sandbox-id-random16chars12.yourdomain.com
// With custom token - URL stays the same across restarts
const stable = await sandbox.exposePort(8080, {
hostname,
token: "api-v1",
});
// https://8080-sandbox-id-api-v1.yourdomain.com
// Same URL after container restart ✓
return Response.json({
"Temporary URL (changes on restart)": exposed.url,
"Stable URL (consistent)": stable.url,
});// Extract hostname from request
const { hostname } = new URL(request.url);
// Without custom token - URL changes on restart
const exposed = await sandbox.exposePort(8080, { hostname });
// https://8080-sandbox-id-random16chars12.yourdomain.com
// With custom token - URL stays the same across restarts
const stable = await sandbox.exposePort(8080, {
hostname,
token: 'api-v1'
});
// https://8080-sandbox-id-api-v1.yourdomain.com
// Same URL after container restart ✓
return Response.json({
'Temporary URL (changes on restart)': exposed.url,
'Stable URL (consistent)': stable.url
});Требования к токену:
- длиной от 1 до 16 символов
- Только строчные буквы (a-z), цифры (0-9), дефисы (-) и символы подчеркивания (_)
- Должно быть уникальным в пределах каждой песочницы
Варианты использования:
- Продакшен-API со стабильными эндпоинтами
- Предоставление URL-адресов демонстраций внешним пользователям
- Интеграционное тестирование с предсказуемыми URL
- Документация с единообразными примерами
Присваивайте имена открытым портам
При работе с несколькими портами используйте имена, чтобы не запутаться:
// Extract hostname from request
const { hostname } = new URL(request.url);
// Start and expose API server with stable token
await sandbox.startProcess("node api.js", { env: { PORT: "8080" } });
await new Promise((resolve) => setTimeout(resolve, 2000));
const api = await sandbox.exposePort(8080, {
hostname,
name: "api",
token: "api-prod",
});
// Start and expose frontend with stable token
await sandbox.startProcess("npm run dev", { env: { PORT: "5173" } });
await new Promise((resolve) => setTimeout(resolve, 2000));
const frontend = await sandbox.exposePort(5173, {
hostname,
name: "frontend",
token: "web-app",
});
console.log("Services:");
console.log("- API:", api.url);
console.log("- Frontend:", frontend.url);// Extract hostname from request
const { hostname } = new URL(request.url);
// Start and expose API server with stable token
await sandbox.startProcess('node api.js', { env: { PORT: '8080' } });
await new Promise(resolve => setTimeout(resolve, 2000));
const api = await sandbox.exposePort(8080, {
hostname,
name: 'api',
token: 'api-prod'
});
// Start and expose frontend with stable token
await sandbox.startProcess('npm run dev', { env: { PORT: '5173' } });
await new Promise(resolve => setTimeout(resolve, 2000));
const frontend = await sandbox.exposePort(5173, {
hostname,
name: 'frontend',
token: 'web-app'
});
console.log('Services:');
console.log('- API:', api.url);
console.log('- Frontend:', frontend.url);Ожидание готовности сервиса
Всегда проверяйте готовность сервиса, прежде чем открывать к нему доступ. В большинстве случаев достаточно простой задержки:
// Extract hostname from request
const { hostname } = new URL(request.url);
// Start service
await sandbox.startProcess("npm run dev", { env: { PORT: "8080" } });
// Wait 2-3 seconds
await new Promise((resolve) => setTimeout(resolve, 2000));
// Now expose
await sandbox.exposePort(8080, { hostname });// Extract hostname from request
const { hostname } = new URL(request.url);
// Start service
await sandbox.startProcess('npm run dev', { env: { PORT: '8080' } });
// Wait 2-3 seconds
await new Promise(resolve => setTimeout(resolve, 2000));
// Now expose
await sandbox.exposePort(8080, { hostname });Для критически важных сервисов опрашивайте конечную точку проверки работоспособности:
// 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 });// 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 });Несколько сервисов
Откройте несколько портов для full-stack приложений:
// 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
});Управление открытыми портами
Список открытых в данный момент портов
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 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);
}Рекомендации
- Ожидание готовности - Не открывайте порты сразу после запуска процессов
- Используйте именованные порты - Проще отслеживать при открытии нескольких портов
- Очистка - Закрывайте доступ к портам, когда они больше не нужны, чтобы не оставлять неиспользуемые URL
- Добавьте аутентификацию - Preview URL общедоступны, поэтому защищайте чувствительные сервисы
Локальная разработка
При локальной разработке с wrangler dev, вы должны открыть порты в своем Dockerfile:
FROM docker.io/cloudflare/sandbox:0.3.3
# Expose ports you plan to use
EXPOSE 8000
EXPOSE 8080
EXPOSE 5173Обновите wrangler.jsonc чтобы использовать ваш Dockerfile:
{
"containers": [
{
"class_name": "Sandbox",
"image": "./Dockerfile"
}
]
}В продакшене все порты доступны и управляются программно через exposePort() / unexposePort().
Устранение неполадок
Порт 3000 зарезервирован
Порт 3000 используется внутренним сервером Bun и не может быть открыт:
// 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 });Порт не готов
Прежде чем открывать доступ, дождитесь запуска сервиса:
// 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 });Порт уже открыт
Проверьте перед публикацией, чтобы избежать ошибок:
// 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 });
}Ошибка: идентификатор песочницы в верхнем регистре
Ошибка: Preview URLs require lowercase sandbox IDs
Причина: Вы создали песочницу с символами в верхнем регистре (например, "MyProject-123") но preview URL при маршрутизации всегда используют нижний регистр, из-за чего возникает несовпадение.
Решение:
// 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 });Это создаёт Durable Object с ID "myproject-123", соответствующей маршрутизации preview URL.
См. Параметры песочницы - normalizeId для получения подробностей.
Формат Preview URL
Production: https://{port}-{sandbox-id}-{token}.yourdomain.com
- Автоматически созданный токен:
https://8080-abc123-random16chars12.yourdomain.com - Пользовательский токен:
https://8080-abc123-my-api-v1.yourdomain.com
Локальная разработка: http://localhost:8787/...
Примечание: Порт 3000 зарезервирован для внутреннего сервера Bun и не может быть открыт наружу.
Дополнительные материалы
- Справочник по API портов - Полный API для открытия портов
- Руководство по фоновым процессам - Управление сервисами
- Руководство по выполнению команд - Запуск сервисов
- Справочник Tunnels API - Рекомендуемая альтернатива для большинства сценариев с публичным URL (быстрые или именованные туннели)