Create CI/CD pipelines that leverage Sandbox SDK for build tools, testing, and code compilation. This example shows how to use sandboxes to provide build environments that Workers don’t include by default.
Overview
Workers are designed for instant JavaScript and WebAssembly execution, but don’t include build tools like:
- npm: Package installation
- Bundlers: esbuild, webpack, rollup
- Compilers: TypeScript, Rust, Go
- Test frameworks: Full test suites
Sandbox SDK provides these tools in isolated containers, enabling complete CI/CD workflows.
The pattern
Build once in sandbox
Run npm install, bundlers, and compilers in a container
Execute many times in Workers
Load the bundled output into Workers for instant execution
Rebuild only when needed
Cache build output until source code changes
Implementation
TypeScript compilation pipeline
Build TypeScript with npm dependencies:
import { getSandbox } from '@cloudflare/sandbox';
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { code, dependencies } = await request.json<{
code?: string;
dependencies?: Record<string, string>;
}>();
if (!code) {
return Response.json({ error: 'Missing code field' }, { status: 400 });
}
const sandbox = getSandbox(env.Sandbox, 'build-pipeline');
// Create package.json with dependencies
const packageJson = {
name: 'user-code',
version: '1.0.0',
dependencies: dependencies || {}
};
await sandbox.writeFile(
'/workspace/package.json',
JSON.stringify(packageJson, null, 2)
);
// Write user's TypeScript code
await sandbox.writeFile('/workspace/index.ts', code);
// Install dependencies
const installResult = await sandbox.exec('npm install', {
cwd: '/workspace'
});
if (!installResult.success) {
return Response.json({
error: 'npm install failed',
stderr: installResult.stderr
}, { status: 500 });
}
// Bundle with esbuild
const bundleResult = await sandbox.exec(
'npx esbuild index.ts --bundle --format=esm --outfile=bundle.js',
{ cwd: '/workspace' }
);
if (!bundleResult.success) {
return Response.json({
error: 'Build failed',
stderr: bundleResult.stderr
}, { status: 500 });
}
// Read the bundled output
const bundle = await sandbox.readFile('/workspace/bundle.js');
return Response.json({
success: true,
bundle: bundle.content
});
}
};
Test execution pipeline
Run tests in isolated environments:
async function runTests(
sandbox: ReturnType<typeof getSandbox>,
testCode: string,
sourceCode: string
): Promise<{ success: boolean; output: string }> {
// Write source and test files
await sandbox.writeFile('/workspace/src/index.ts', sourceCode);
await sandbox.writeFile('/workspace/tests/index.test.ts', testCode);
// Create package.json with test dependencies
await sandbox.writeFile(
'/workspace/package.json',
JSON.stringify({
name: 'test-suite',
scripts: {
test: 'vitest run'
},
devDependencies: {
vitest: '^1.0.0',
'@types/node': '^20.0.0'
}
}, null, 2)
);
// Install dependencies
await sandbox.exec('npm install', { cwd: '/workspace' });
// Run tests
const testResult = await sandbox.exec('npm test', { cwd: '/workspace' });
return {
success: testResult.success,
output: testResult.success ? testResult.stdout : testResult.stderr
};
}
Multi-stage build pipeline
Implement a complete CI/CD workflow:
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { repository, branch } = await request.json<{
repository?: string;
branch?: string;
}>();
if (!repository) {
return Response.json({ error: 'Missing repository' }, { status: 400 });
}
const sandbox = getSandbox(
env.Sandbox,
crypto.randomUUID().slice(0, 8)
);
const results = {
clone: { success: false, output: '' },
install: { success: false, output: '' },
test: { success: false, output: '' },
build: { success: false, output: '' }
};
try {
// Stage 1: Clone repository
await sandbox.gitCheckout(repository, {
targetDir: 'repo',
branch: branch || 'main'
});
results.clone = { success: true, output: 'Repository cloned' };
// Stage 2: Install dependencies
const installResult = await sandbox.exec('npm install', {
cwd: '/workspace/repo'
});
results.install = {
success: installResult.success,
output: installResult.success ? installResult.stdout : installResult.stderr
};
if (!installResult.success) {
return Response.json({ results }, { status: 500 });
}
// Stage 3: Run tests
const testResult = await sandbox.exec('npm test', {
cwd: '/workspace/repo'
});
results.test = {
success: testResult.success,
output: testResult.success ? testResult.stdout : testResult.stderr
};
if (!testResult.success) {
return Response.json({ results }, { status: 500 });
}
// Stage 4: Build
const buildResult = await sandbox.exec('npm run build', {
cwd: '/workspace/repo'
});
results.build = {
success: buildResult.success,
output: buildResult.success ? buildResult.stdout : buildResult.stderr
};
return Response.json({ results });
} catch (error) {
return Response.json({
results,
error: error instanceof Error ? error.message : 'Unknown error'
}, { status: 500 });
}
}
};
Dynamic Worker execution
Combine sandbox builds with Dynamic Workers:
import { WorkerEntrypoint } from 'cloudflare:workers';
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// Build endpoint: compile user code
if (url.pathname === '/build' && request.method === 'POST') {
const { code } = await request.json<{ code?: string }>();
if (!code) {
return Response.json({ error: 'Missing code' }, { status: 400 });
}
const sandbox = getSandbox(env.Sandbox, 'builder');
// Write and bundle code
await sandbox.writeFile('/workspace/index.ts', code);
const bundleResult = await sandbox.exec(
'npx esbuild index.ts --bundle --format=esm --outfile=bundle.js',
{ cwd: '/workspace' }
);
if (!bundleResult.success) {
return Response.json({
error: 'Build failed',
stderr: bundleResult.stderr
}, { status: 500 });
}
const bundle = await sandbox.readFile('/workspace/bundle.js');
// Store bundle in KV or R2 for later execution
const bundleId = crypto.randomUUID();
await env.BUNDLES.put(bundleId, bundle.content);
return Response.json({ bundleId });
}
// Execute endpoint: run compiled code in Dynamic Worker
if (url.pathname.startsWith('/execute/')) {
const bundleId = url.pathname.split('/')[2];
const bundle = await env.BUNDLES.get(bundleId);
if (!bundle) {
return Response.json({ error: 'Bundle not found' }, { status: 404 });
}
// Load and execute the bundle in a Dynamic Worker
const worker = await env.DYNAMIC_WORKER.get(bundleId);
return worker.fetch(request);
}
return new Response('Not Found', { status: 404 });
}
};
Example usage
Build TypeScript code
curl -X POST http://localhost:8787/build \
-H "Content-Type: application/json" \
-d '{
"code": "import { z } from \"zod\";\nexport const schema = z.object({ name: z.string() });",
"dependencies": { "zod": "^3.22.0" }
}'
Run CI/CD pipeline
curl -X POST http://localhost:8787/pipeline \
-H "Content-Type: application/json" \
-d '{
"repository": "https://github.com/user/repo",
"branch": "main"
}'
Execute tests
curl -X POST http://localhost:8787/test \
-H "Content-Type: application/json" \
-d '{
"testCode": "import { expect, test } from \"vitest\";\ntest(\"adds 1 + 2\", () => { expect(1 + 2).toBe(3); });"
}'
Use cases
- Code Playgrounds: Interactive coding environments with instant compilation
- Validation Services: Validate user-submitted code before deployment
- Build Services: On-demand building and bundling
- Test Runners: Execute test suites in isolated environments
- Code Transformation: Transpile, minify, and optimize code
Best practices
Cache build outputs to avoid rebuilding unchanged code. Use content hashing to detect changes.
Set appropriate timeouts for build steps. Complex builds may take longer than simple compilation.
Use separate sandboxes for different build jobs to avoid conflicts and ensure isolation.
Combining with Dynamic Workers
The real power comes from combining sandbox builds with Dynamic Workers:
- Sandbox SDK: Provides build tools (npm, esbuild, etc.)
- Dynamic Workers: Execute the bundled output instantly
- Result: User code with npm dependencies running in Workers
This pattern enables:
- Interactive code playgrounds
- User-submitted Workers
- Custom validation logic
- Serverless functions as a service