Cloudflare Dynamic Workers — secure code execution at 2ms cold start
V8 isolates vs containers — I built a sandbox demo and measured the difference. 100x faster isn't marketing, it's real.
My CEO forwarded me a Cloudflare blog post about Dynamic Workers. Two days later I had a live demo running. Here's what I learned.
$ The problem Dynamic Workers solves
If you want to run arbitrary code safely — code generated by an AI, submitted by a user, whatever — the industry default is containers. Spin one up, run the code, tear it down. It works. It's also slow and expensive at scale.
The alternative: V8 isolates.
Instead of spinning up a full container with its own OS, filesystem, and network stack, you spin up a V8 isolate — the same sandbox Chrome uses to run JavaScript tabs without letting one tab read another tab's memory.
Container cold start → ~1500ms
V8 isolate cold start → ~2ms
That's not a claim from Cloudflare's marketing page. That's what I measured in the benchmark endpoint I built.
$ What Dynamic Workers actually is
A normal Cloudflare Worker is one isolate. Code is fixed at deploy time. It runs until you redeploy.
Dynamic Workers lets your Worker spin up a new isolate at runtime with any code you choose:
Your Worker (trusted) Dynamic Worker (sandbox)
───────────────────── ──────────────────────────
Has secrets No secrets
Has env variables No internet access
Has RPC tools Only tools you explicitly injected
│ │
└──── env.LOADER.load() ────────►│
~2ms runs untrusted code
thrown away after
The key properties:
- globalOutbound: null — the sandbox has zero internet access. Any fetch() inside throws immediately.
- RPC tool injection — you pass typed functions into the sandbox. The sandbox calls them like local functions, but they execute on the host side. Your secrets never enter the sandbox.
- Automatic cleanup — isolate is thrown away after execution. No state leaks between runs.
$ What I built
Four endpoints to demonstrate the key properties:
// POST /run — execute arbitrary JS in a fresh V8 isolate
app.post('/run', async (c) => {
const { code } = await c.req.json()
const worker = await c.env.LOADER.load(code)
const result = await worker.run()
return c.json({ result })
})
// POST /run-tool — same sandbox + typed RPC tools injected
// GET /benchmark?n=50 — spin 50 sandboxes concurrently
// GET /health — liveness
The benchmark endpoint is the convincing one. Spin 50 sandboxes concurrently, measure wall-clock time, return the results. Every time I ran it: ~15ms total. That's 50 isolates in the time one container is still booting.
$ Security properties that matter
Internet is blocked at the isolate level — not application level.
This matters because application-level blocking is bypassable. If you try to block fetch() in application code, someone can find another way out. globalOutbound: null is enforced by the V8 runtime itself. There is no workaround from inside the sandbox.
Secrets stay on the host side.
// This is what RPC tool injection looks like
const tools = {
getSecret: () => c.env.MY_SECRET, // runs on host
callDatabase: (query) => db.run(query) // runs on host
}
const worker = await c.env.LOADER.load(untrustedCode, { tools })
// untrustedCode can call tools.getSecret()
// but never sees the actual secret value directly
The untrusted code can use your tools. It cannot inspect them, modify them, or extract the secrets they wrap.
Cloudflare patches V8 security vulnerabilities faster than you can.
Chrome V8 patches ship to Cloudflare's production infrastructure within hours. If you're running your own V8 sandbox on your own infrastructure, you're responsible for tracking and applying those patches yourself. That's a non-trivial security maintenance burden.
$ The use case that makes this interesting
AI agent code execution.
The LLM generates JavaScript. It runs in the sandbox. Results come back. The LLM never touches your secrets, your database, your internal APIs — unless you explicitly inject those as RPC tools with the exact interface you want to expose.
LLM generates code
↓
POST /run { code: "..." }
↓
Fresh V8 isolate — no internet, no secrets
↓
Only has access to tools you injected
↓
Returns result
↓
Isolate destroyed
Compare this to running LLM-generated code in a container: you need to build the container, push it, pull it, start it, wait for it to be ready, run the code, tear it down. By which point the V8 approach has already run 50 requests.
$ Deploying it
tar xzf dynamic-workers-demo.tar.gz
cd dynamic-workers-demo
npm install
npx wrangler login
npm run deploy
One requirement: Workers Paid plan ($5/month). Dynamic Worker Loader is in open beta — during beta the $0.002/sandbox/day charge is waived.
$ What I'd use this for
- Any system where an LLM writes and runs code
- User-submitted automation scripts
- Plugin systems where third-party code runs in your infrastructure
- Chaos engineering — run destructive test scripts without giving them access to anything real
The 2ms cold start is fast enough that you can create a fresh sandbox per request without the user noticing. That's the key difference from containers — you don't need to keep sandboxes warm.
$ One caveat
Dynamic Workers is still in open beta. The API could change. I wouldn't build a production critical path on it today — but I'd absolutely prototype on it and watch it closely.
Source code for the demo is on $ The problem Dynamic Workers solves
If you want to run arbitrary code safely — code generated by an AI, submitted by a user, whatever — the industry default is containers. Spin one up, run the code, tear it down. It works. It's also slow and expensive at scale. The alternative: V8 isolates. Instead of spinning up a full container with its own OS, filesystem, and network stack, you spin up a V8 isolate — the same sandbox Chrome uses to run JavaScript tabs without letting one tab read another tab's memory. That's not a claim from Cloudflare's marketing page. That's what I measured in the benchmark endpoint I built. A normal Cloudflare Worker is one isolate. Code is fixed at deploy time. It runs until you redeploy. Dynamic Workers lets your Worker spin up a new isolate at runtime with any code you choose:Container cold start → ~1500ms
V8 isolate cold start → ~2ms$ What Dynamic Workers actually is
Dynamic Worker (sandbox)
───────────────────── ──────────────────────────
Has secrets No secrets
Has env variables No internet access
Has RPC tools Only tools you explicitly injected
│ │
└──── env.LOADER.load() ────────►│
~2ms runs untrusted code
thrown away after
Your Worker (trusted">GitHub
The key properties:
- globalOutbound: null — the sandbox has zero internet access. Any fetch() inside throws immediately.
- RPC tool injection — you pass typed functions into the sandbox. The sandbox calls them like local functions, but they execute on the host side. Your secrets never enter the sandbox.
- Automatic cleanup — isolate is thrown away after execution. No state leaks between runs.
$ What I built
Four endpoints to demonstrate the key properties:
// POST /run — execute arbitrary JS in a fresh V8 isolate
app.post('/run', async (c) => {
const { code } = await c.req.json()
const worker = await c.env.LOADER.load(code)
const result = await worker.run()
return c.json({ result })
})
// POST /run-tool — same sandbox + typed RPC tools injected
// GET /benchmark?n=50 — spin 50 sandboxes concurrently
// GET /health — liveness
The benchmark endpoint is the convincing one. Spin 50 sandboxes concurrently, measure wall-clock time, return the results. Every time I ran it: ~15ms total. That's 50 isolates in the time one container is still booting.
$ Security properties that matter
Internet is blocked at the isolate level — not application level.
This matters because application-level blocking is bypassable. If you try to block fetch() in application code, someone can find another way out. globalOutbound: null is enforced by the V8 runtime itself. There is no workaround from inside the sandbox.
Secrets stay on the host side.
// This is what RPC tool injection looks like
const tools = {
getSecret: () => c.env.MY_SECRET, // runs on host
callDatabase: (query) => db.run(query) // runs on host
}
const worker = await c.env.LOADER.load(untrustedCode, { tools })
// untrustedCode can call tools.getSecret()
// but never sees the actual secret value directly
The untrusted code can use your tools. It cannot inspect them, modify them, or extract the secrets they wrap.
Cloudflare patches V8 security vulnerabilities faster than you can.
Chrome V8 patches ship to Cloudflare's production infrastructure within hours. If you're running your own V8 sandbox on your own infrastructure, you're responsible for tracking and applying those patches yourself. That's a non-trivial security maintenance burden.
$ The use case that makes this interesting
AI agent code execution.
The LLM generates JavaScript. It runs in the sandbox. Results come back. The LLM never touches your secrets, your database, your internal APIs — unless you explicitly inject those as RPC tools with the exact interface you want to expose.
LLM generates code
↓
POST /run { code: "..." }
↓
Fresh V8 isolate — no internet, no secrets
↓
Only has access to tools you injected
↓
Returns result
↓
Isolate destroyed
Compare this to running LLM-generated code in a container: you need to build the container, push it, pull it, start it, wait for it to be ready, run the code, tear it down. By which point the V8 approach has already run 50 requests.
$ Deploying it
tar xzf dynamic-workers-demo.tar.gz
cd dynamic-workers-demo
npm install
npx wrangler login
npm run deploy
One requirement: Workers Paid plan ($5/month). Dynamic Worker Loader is in open beta — during beta the $0.002/sandbox/day charge is waived.
$ What I'd use this for
- Any system where an LLM writes and runs code
- User-submitted automation scripts
- Plugin systems where third-party code runs in your infrastructure
- Chaos engineering — run destructive test scripts without giving them access to anything real
The 2ms cold start is fast enough that you can create a fresh sandbox per request without the user noticing. That's the key difference from containers — you don't need to keep sandboxes warm.
$ One caveat
Dynamic Workers is still in open beta. The API could change. I wouldn't build a production critical path on it today — but I'd absolutely prototype on it and watch it closely.
Source code for the demo is on GitHub.
Thoughts or questions → [[email protected]].
Thoughts or questions → [email protected]
-- null