Skip to content

Cloudflare Compute Primitives

Disponible en francais

Cloudflare offers three distinct compute primitives on the Workers platform. Choosing the right one early prevents expensive migrations later.

PrimitiveBest forAvoid when
WorkerApp logic, APIs, middleware, routing, lightweight background tasksYou need a full runtime, filesystem, or Linux environment
Dynamic WorkerRunning untrusted or AI-generated code in a sandboxYou need CPU-heavy compute, a full filesystem, or existing container images
ContainerHeavy workloads, existing container images, WebSocket servers, cron jobs, Region:EarthYou only need simple request handling or AI inference

The default primitive. Deploy JavaScript or TypeScript to Cloudflare’s edge with fast cold starts and no infrastructure management.

  • HTTP handling, API routes, middleware
  • Background tasks via Queues or Workflows
  • Durable Object stateful services
  • R2, D1, KV, Vectorize bindings
  • Workers AI inference
export default {
async fetch(request: Request, env: Env): Promise<Response> {
return new Response("Hello from a Worker");
},
};

Spin up isolated Workers at runtime to execute arbitrary code in a secure sandbox. The parent Worker controls which bindings the dynamic Worker receives and whether it can reach the network.

  • AI agent code execution: Let an agent write and run its own tools safely.
  • Untrusted user code: Run code submitted by users in a sandbox you control.
  • Previews and playgrounds: Load generated code in milliseconds.
  • Custom automations: Create tools on the fly for one-off tasks.
  • “Vibe coding”: Run AI-generated prototypes in isolation.

Capability-based security via Workers RPC Bindings use Cap’n Web RPC. A dynamic Worker only gets access to what you explicitly pass as a stub. If you never pass a binding, it cannot be reached.

Egress control Set globalOutbound: null to block all outbound network access. The dynamic Worker can only use the bindings you give it. Or intercept and rewrite requests through a gateway.

Tail Worker observability Attach a Tail Worker to capture console.log, exceptions, and request metadata from the dynamic Worker. Logs are written after the response returns, so they add no latency.

import { getContainer } from "@cloudflare/containers";
// Dynamic Worker class
export class MyContainer extends Container {
defaultPort = 4000;
sleepAfter = "10m";
}
export default {
async fetch(request, env) {
const { "session-id": sessionId } = await request.json();
const containerInstance = getContainer(env.MY_CONTAINER, sessionId);
return containerInstance.fetch(request);
},
};
wrangler.jsonc
{
"name": "container-starter",
"main": "src/index.js",
"compatibility_date": "2026-03-30",
"containers": [
{
"class_name": "MyContainer",
"image": "./Dockerfile",
"max_instances": 5
}
],
"durable_objects": {
"bindings": [
{ "class_name": "MyContainer", "name": "MY_CONTAINER" }
]
},
"migrations": [
{ "new_sqlite_classes": ["MyContainer"], "tag": "v1" }
]
}

Security Pattern: Custom Binding for Agent Sandbox

Section titled “Security Pattern: Custom Binding for Agent Sandbox”
import { WorkerEntrypoint } from "cloudflare:workers";
// Define a narrow capability the agent can use
export class ChatRoom extends WorkerEntrypoint<Env, ChatRoomProps> {
async post(text: string): Promise<void> {
// Rewrite with bot identity before sending
text = `[${this.ctx.props.botName}]: ${text}`;
await postToChat(this.ctx.props.apiKey, this.ctx.props.roomName, text);
}
}
type ChatRoomProps = {
apiKey: string;
roomName: string;
botName: string;
};

The agent only sees the ChatRoom.post() method. It never sees the API key or can post to any other room.

const worker = env.LOADER.get(id, () => ({
mainModule: "index.js",
modules: { "index.js": code },
globalOutbound: null, // no network access
}));

The dynamic Worker can only act through the bindings you pass it.

export class DynamicWorkerTail extends WorkerEntrypoint {
async tail(events) {
for (const event of events) {
for (const log of event.logs) {
console.log({
source: "dynamic-worker-tail",
workerId: this.ctx.props.workerId,
level: log.level,
message: log.message,
});
}
}
}
}
// Attach when creating the dynamic Worker
const worker = env.LOADER.get(workerId, () => ({
mainModule: WORKER_MAIN,
modules: { [WORKER_MAIN]: WORKER_SOURCE },
tails: [ctx.exports.DynamicWorkerTail({ props: { workerId } })],
}));

Run code written in any language, built for any runtime, as part of a Workers app. Container instances spin up on-demand and are controlled by your Worker code.

Available on Workers Paid plan.

  • Resource-intensive workloads: CPU cores in parallel, large memory or disk needs.
  • Full filesystem access: Applications that need a real Linux-like environment.
  • Existing container images: Tools distributed as Docker images.
  • WebSocket servers: Long-lived connections that do not fit the Workers model.
  • Scheduled jobs: Run a container on a cron schedule with CRON Triggers.
  • Region:Earth compliance: Data residency requirements.
PatternUse case
Static frontend + Container backendSPA with a containerized API backend
Cron ContainerScheduled workloads on a cron trigger
Durable Object interfaceCall containers directly from DOs
Env vars and secretsPass credentials securely into containers
Stateless instancesScale across Cloudflare’s network
Status hooksReact to container lifecycle events
Websocket to ContainerForward WebSocket connections to a container
R2 FUSE mountMount R2 buckets as filesystems inside a container
wrangler.jsonc
{
"name": "my-container-app",
"main": "src/index.js",
"compatibility_date": "2026-03-30",
"containers": [
{
"class_name": "MyContainer",
"image": "./Dockerfile",
"max_instances": 5
}
],
"durable_objects": {
"bindings": [
{ "class_name": "MyContainer", "name": "MY_CONTAINER" }
]
}
}
import { Container, getContainer } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 8080;
sleepAfter = "5m";
}
export default {
async fetch(request, env) {
const id = request.headers.get("X-Session-Id") ?? "default";
const instance = getContainer(env.MY_CONTAINER, id);
return instance.fetch(request);
},
};

Choosing Between Dynamic Worker and Container

Section titled “Choosing Between Dynamic Worker and Container”
DimensionDynamic WorkerContainer
Startup speedSub-millisecondSeconds to minutes
RuntimeV8 isolates (JavaScript/TypeScript)Full Linux container
Security modelCapability-based RPC, no network by defaultNetwork isolation configurable
Use code generationYes — agent writes code at runtimeNo — image must exist beforehand
CPU/memoryLightweightFull CPU and memory access
FilesystemNoFull filesystem access
Best fitAI agent sandbox, untrusted code, fast previewsExisting images, heavy workloads, WebSockets

Rule of thumb: use Dynamic Workers when the Worker writes or generates the code. Use Containers when you need to ship and run a pre-built image.

  1. Block network by default: Set globalOutbound: null and only grant access via narrow bindings.
  2. Use capability-based bindings: Pass stubs with only the methods the Worker needs, not raw credentials.
  3. Inject credentials at the gateway: Never expose secrets to the dynamic Worker; rewrite or inject at the boundary.
  4. Use Tail Workers for observability: Capture logs without adding request latency.