INTEGRITY Документация

Публикация сервисов

В этом руководстве показано, как открыть сервисам, работающим в вашей песочнице, доступ из интернета через preview URL.

Когда открывать порты

Открывайте порты, когда нужно:

Базовое открытие портов

Типичный порядок работы такой: запустите сервис → дождитесь готовности → откройте порт → обрабатывайте запросы с помощью 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
});

Требования к токену:

Варианты использования:

Присваивайте имена открытым портам

При работе с несколькими портами используйте имена, чтобы не запутаться:

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

Рекомендации

Локальная разработка

При локальной разработке с wrangler dev, вы должны открыть порты в своем Dockerfile:

Dockerfile
FROM docker.io/cloudflare/sandbox:0.3.3

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

Обновите wrangler.jsonc чтобы использовать ваш Dockerfile:

wrangler.jsonc
{
  "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

Локальная разработка: http://localhost:8787/...

Примечание: Порт 3000 зарезервирован для внутреннего сервера Bun и не может быть открыт наружу.