Modal vs Vercel Sandbox for Running Untrusted Code
Modal and Vercel Sandbox arrive on the same shortlist from opposite directions. Modal grew up as Python-first serverless compute for the ML crowd: batch jobs, GPU work, wide fan-out, a decorator that makes the machine disappear. Vercel Sandbox grew out of the frontend and deploy world as an ephemeral compute primitive sitting next to the platform your app already ships to. Neither was designed as "the place AI agents run untrusted code." Both ended up there anyway, because that's where the demand went, and now people compare them.
I'm Ajay, I built PandaStack, which is also in this category — so read accordingly. My rule for these posts is simple: numbers only for the platform I actually operate and can measure, qualitative descriptions for everyone else. I'm not going to publish a benchmark of someone else's service or quote a price that will be wrong before this post is a month old. There's a section near the end where I recommend both of them over me, under conditions that are common.
Who each one is actually for
Modal: your Python function, at scale, machine not included
Modal's design centre is the decorated Python function. You describe the container image in the same file as the code that runs in it, decorate the function, deploy, and the platform handles build, distribution and execution — once, or across a very wide fan-out. The batteries it ships reflect the workloads that motivated it: GPU attachment, long-running batch and ML jobs, scheduled work, large parallel maps over big inputs. If your sentence is "here is a function, run it on hardware I refuse to think about," Modal was built to answer exactly that sentence and it answers it well.
Notice the direction of the abstraction, because it's the whole trade. Modal wants your code to come to it, defined in its idiom. That's a real ergonomic win — image-as-code means there's no separate Dockerfile drifting out of sync with your imports, and no cluster to keep alive. It's also the constraint: the deploy step is part of the model. When the code you want to run was generated moments ago by an LLM, "define the environment as code and deploy it" is a step you now have to perform at runtime, on behalf of a caller, for code you've never seen.
Vercel Sandbox: ephemeral compute where your app already lives
Vercel Sandbox comes at the problem from the deploy side. It exists because Vercel's customers were already generating code inside their applications — app builders, agent features, user-submitted snippets — and had nowhere safe to execute it that wasn't a second vendor, a second bill and a second auth story. The primitive is an ephemeral box you create from your app, put files into, run a command in, and discard. It's shaped like a request, because the platform around it is shaped like a request.
The gravity here is integration, and integration gravity is a genuine engineering asset when it pulls the way you were already going. If your product deploys to Vercel, your team lives in that dashboard, and the job is "run this generated snippet and show the user the result," you can be in production in an afternoon without adding a vendor. The flip side is that the primitive's assumptions come from the platform: it inherits the platform's mental model of how long work should take and where it should run.
Modal asks you to bring your function to its runtime. Vercel Sandbox hands you a box next to your deploy. Agents want neither of those things exactly — they want a computer for a while.
The axes that actually decide it
Here's the comparison across the dimensions that bite six months in, when the demo has become a product. Competitor columns are deliberately qualitative; PandaStack is in every line so you can see where it sits rather than infer it.
- Environment definition — Modal: image-as-code in Python, built and versioned by the platform, which is excellent when the environment is authored ahead of time and awkward when it's decided at runtime. Vercel Sandbox: the platform's runtimes plus whatever you install at session start, which is simple but puts dependency installation on the critical path of every single run. PandaStack: environments are baked into Firecracker snapshots (base, code-interpreter, agent, browser, postgres-16, or a custom template from your own Dockerfile), so the box is already warm when it's restored rather than assembled at boot.
- Language and runtime fit — Modal: Python-first by design; the decorator model is the API, and non-Python work means driving a subprocess from inside a Python function. Vercel Sandbox: strongest on the JS/TS end of the platform's world with Python available; if your agent emits Rust or wants a system package manager, test that before you design around it. PandaStack: a full Ubuntu 24.04 guest with a shell, so "install the thing" is an apt/pip/npm problem rather than a platform question, with Node, Python, Go and Bun pre-warmed via mise.
- Unit of work — Modal: an invocation of a function you authored and deployed. Vercel Sandbox: an ephemeral box created per piece of work and thrown away. PandaStack: a whole microVM created per task, kept for as long as you want it, and destroyed — with the option to snapshot it and bring it back later.
- Session length and statefulness — Modal: ephemeral per invocation; durable state is an explicit, deliberate add-on rather than the default. Vercel Sandbox: bounded by a documented ceiling that fits a request-shaped platform — if your job is a forty-minute build, check that number first. PandaStack: a sandbox lives until its ttl_seconds expires, you kill it, or the idle reaper takes it; persistent sandboxes are exempt from the reaper entirely.
- Filesystem persistence — Modal: scratch by default, with volumes and object storage as the explicit durable path. Vercel Sandbox: ephemeral by design; treat anything on disk as gone when the session ends. PandaStack: the rootfs is a copy-on-write clone that persists for the VM's life, snapshots capture disk and memory together, and durable volumes exist for state that must outlive the machine.
- Cold start shape — Modal: optimised hard around function start, with warm execution environments doing a lot of the work between calls. Vercel Sandbox: optimised around creating a box quickly inside a request budget. PandaStack: every create is a snapshot restore, not a boot — p50 179ms end to end, p99 around 203ms, with the restore step itself roughly 49ms; only the very first spawn of a template does a real ~3s boot. Check the other two against your own workload from your own region; don't take anyone's marketing number, mine included.
- Network egress control — Modal and Vercel Sandbox: both document their network behaviour, and both deserve ten minutes of your reading before you assume you can pin a sandbox to one allowed endpoint. Egress policy on managed compute is frequently coarser than a security review expects. PandaStack: every sandbox gets its own network namespace with its own veth pair and NAT rules, which is the layer where allow-list egress is actually enforceable rather than aspirational.
- GPU availability — Modal: first-class and well developed; for a lot of teams it is the entire reason they chose it. Vercel Sandbox: not what it's for; it's a compute primitive next to a deploy platform. PandaStack: CPU sandboxing only — if your unit of work is a model forward pass, this row ends the comparison.
- Pricing shape — Modal: metered around invocations and the resources they consume, including accelerators. Vercel Sandbox: bundled into the surrounding platform's billing relationship, which is convenient and makes per-workload attribution harder. PandaStack: metered on how long the VM actually lives and how much CPU it actually burns, so a four-second task costs four seconds. All three of these have changed at least once; read the pricing pages, not this bullet.
The two developer experiences, side by side
The fastest way to feel the difference is to look at the shape of each model rather than the feature list. Both sketches below are illustrative — they show how each product wants you to think, not APIs you should copy. Symbol names in someone else's SDK are exactly the kind of detail that goes stale, so check the current docs for anything you plan to type.
# ---- ILLUSTRATIVE SKETCH of the Modal-shaped model ----
# Not copy-paste-ready. Verify exact API names in Modal's current docs.
# 1. The environment is declared as code, in the same file as the code.
image = Image.base().pip_install("pandas", "matplotlib")
# 2. A decorator binds an ordinary Python function to that environment.
@app.function(image=image)
def summarize(rows):
import pandas as pd
return pd.DataFrame(rows).describe().to_string()
# 3. Deploy the app once; afterwards you just call the function.
# Note what is absent from this file: any machine at all.
# That absence is the product. It is also the thing that makes
# "run this code the model just wrote" an awkward fit, because
# the environment is authored ahead of time, not decided at call time.// ---- ILLUSTRATIVE SKETCH of the ephemeral-sandbox shape ----
// Vercel Sandbox and most sandbox APIs land somewhere near this.
// Indicative only; check the current docs before typing any of it.
const box = await Sandbox.create({ /* runtime, resources, timeout */ });
// The generated code goes IN, rather than the environment being
// declared ahead of time. This is the shape agents actually want.
await box.writeFiles([{ path: "step.py", content: modelOutput }]);
const run = await box.runCommand({ cmd: "python", args: ["step.py"] });
console.log(await run.stdout());
await box.stop(); // and the box, and everything in it, is goneThe second shape is the one an agent loop wants, and it's worth being explicit about why: an agent decides what to run after you've already provisioned the compute. A deploy-first model asks you to know the environment before the model has spoken. You can absolutely bridge that gap — people do, by baking a generous image and shelling out from inside a function — but you're then using a function platform as a very expensive shell, and paying its ergonomics tax without collecting its benefit.
Fit inside an agent loop, not as a deploy target
This is the axis I'd weight highest and the one comparison tables usually miss. A deploy target is something you set up occasionally and then use. An agent loop is something that provisions, runs, fails, reads the failure, and runs again — dozens of times inside one user request, with each step depending on the previous step's side effects on disk.
Three questions separate products that are pleasant inside that loop from products that fight it. First: can the environment be decided at call time, or must it be declared and deployed in advance? Second: does state survive between steps, or does step twelve have to reconstruct what step eleven did? Third: how many times does your loop pay the start cost — once per session, or once per tool call? A three-second start paid once per session is invisible. A three-hundred-millisecond start paid on forty tool calls is your entire latency budget, spent on nothing.
Where PandaStack fits (the vendor section — labelled as such)
Between "a function that vanishes" and "a box shaped like a request" there's a third option, and it only becomes viable if creating a real machine is genuinely cheap: a hardware-isolated microVM you create for one task, use, and destroy. No warm pool, no reuse, no residue.
That's what PandaStack is. Every sandbox is its own Firecracker microVM with its own guest kernel and its own network namespace — each host pre-allocates 16,384 /30 subnets so network setup never lands in the hot path. The usual objection to VMs is start time, but that's a boot-path problem rather than a virtualization problem: every create restores a baked snapshot instead of cold-booting, giving a p50 of 179ms end to end and a p99 around 203ms, of which the restore step is roughly 49ms. The only ~3s boot is the first spawn of a template, before its snapshot exists.
Why that matters isn't the latency, it's the economics of isolation. When a fresh machine costs a fifth of a second, you stop reusing them — and reuse is where sandbox security bugs actually live: the leftover file in /tmp, the cached credential, the background process that outlived its task. "Destroy it" is a much easier invariant to hold than "clean it thoroughly," because cleanup code is the least-run path in any system and is somehow always asked to be perfect.
from pandastack import Sandbox
def agent_turn(question: str, model_code: str) -> str:
"""One turn of an agent loop: give it a real machine, then burn it."""
sbx = Sandbox.create(
template="code-interpreter",
ttl_seconds=900, # dead-man's switch, not a nicety
metadata={"turn": question[:64]},
)
try:
# The model's output goes INTO the guest. The host never executes it.
sbx.filesystem.write("/work/step.py", model_code)
run = sbx.exec("cd /work && python3 step.py", timeout_seconds=300)
if run.exit_code != 0:
# The stderr is the useful part -- it goes back to the model.
return f"failed:\n{run.stderr[-2000:]}"
# Freeze the warmed machine so the NEXT turn starts from here
# instead of reinstalling three gigabytes of wheels again.
snap = sbx.snapshot()
# Branch it: copy-on-write memory + reflinked disk.
# 400-750ms same-host, 1.2-3.5s cross-host.
branch = snap.fork()
try:
branch.exec("cd /work && pytest -q", timeout_seconds=600)
finally:
branch.kill() # a bad branch never poisons the parent
return sbx.filesystem.read("/work/out/answer.md")
finally:
sbx.kill() # files, processes, network namespace: gone togetherTwo lines there do the load-bearing work. ttl_seconds exists because your orchestrator will eventually die between create and kill, and the VM still has to go; never trust your own cleanup path to be reached. And snapshot/fork is the operation neither a function platform nor a request-shaped box typically exposes: branching a machine that is already warm, with its dependency tree installed and its page cache populated, so eight candidate attempts cost one install rather than eight.
When PandaStack is the wrong answer
- You need GPUs — we don't have them. Modal does, seriously and well. If your unit of work is a forward pass or a fine-tune, this comparison ended several sections ago and the answer is Modal.
- You're already all-in on Vercel and the workload is short — adding a second vendor to run a snippet for four seconds buys you a second bill and a second on-call rotation. Integration gravity is worth real money when it points where you were going anyway.
- The container image IS the product — image-as-code is a genuine ergonomic win, and reimplementing it on top of a generic sandbox API is work nobody should volunteer for.
- Trusted first-party code with no isolation requirement — if you wrote it, reviewed it, and it only touches your own data, a hardware boundary per task is a cost with no matching benefit.
- You want zero infrastructure concepts in your head — a microVM API answers "I never want to think about a machine" by handing you a machine. That's the wrong tool, on purpose.
Which should you pick
- Pick Modal if the unit of work is a Python function you authored, especially with a GPU, a large batch, or a wide fan-out attached. Its abstraction is genuinely good at making infrastructure disappear for code you own and trust, and nothing else in this post competes with it on accelerators.
- Pick Vercel Sandbox if your app already deploys to Vercel and the job is short, request-shaped code execution — a generated snippet, a user-supplied transform, a preview build step. The zero-integration-cost argument is real, and "one vendor" is an underrated architecture property.
- Pick a microVM sandbox API like PandaStack if you're executing code you don't trust, want a documented hardware kernel boundary per task, need sessions that outlive a request budget, or want to fork a warmed environment to explore several branches at once.
- Pick two — this is common and correct. A function platform for the GPU and batch tier where you own the code; a sandbox API for the untrusted per-task tier where you don't. They aren't competing for the same request, and pretending they are is how you end up with one tool doing two jobs badly.
- Decide by running the thing, not by reading tables. Build the smallest workload that would embarrass you in production: create compute, install your two ugliest dependencies, run your longest realistic job, try to reach an endpoint you're supposed to be blocked from, and tear it all down. Time each step from the region you'll actually deploy to. An afternoon of that beats any comparison post, this one very much included.
If you want the single-vendor detail instead of the shape argument, /blog/pandastack-vs-modal and /blog/pandastack-vs-vercel-sandbox go head-to-head with each of them directly. /blog/modal-vs-daytona covers the function-versus-workspace axis, /blog/vercel-sandbox-vs-e2b covers platform-native versus provider-neutral, and /blog/best-code-execution-sandboxes surveys the wider field if none of these three fit.
Frequently asked questions
What is the main difference between Modal and Vercel Sandbox?
They come from different worlds and it shows in the primitive each one gives you. Modal is Python-first serverless compute built around a decorated function whose container image is declared as code alongside it, then deployed — the machine is deliberately absent from your mental model, and GPU and batch work are first-class. Vercel Sandbox is an ephemeral compute box you create from your application, put files into, run a command in, and discard, living next to the platform your app already deploys to. Modal suits code you authored and want run at scale; Vercel Sandbox suits short, request-shaped execution of code decided at runtime, with near-zero integration cost if you're already on Vercel. Verify current capabilities and pricing in each vendor's docs — this category changes quickly.
Which is better for running AI-agent-generated code?
Look at when the environment gets decided. An agent decides what to run after the compute has been provisioned, so an ephemeral create-then-run primitive fits the loop more naturally than a declare-and-deploy model — that favours a sandbox-shaped API over a function-shaped one for the untrusted execution tier specifically. Modal can serve the case by baking a generous image and shelling out from inside a function, but you're then paying a deploy-model's ergonomics without collecting its benefit. The other question to answer before choosing is how long a session can live and whether state survives between agent steps, because a loop that rebuilds its working directory every step is where the bugs are.
Do I need hardware-level isolation to run model-generated code?
If the code came from outside your trust boundary, treat it as untrusted in the formal sense — not "probably fine." The question to ask any vendor is where exactly the kernel boundary sits and who else is inside it, because if the boundary is a shared kernel then a single kernel CVE is a shared-fate event across every tenant on that host, and container escapes are a recurring annual genre rather than a hypothetical. A hardware-virtualized boundary gives each workload its own guest kernel and shrinks the attack surface exposed to the guest from the entire Linux syscall interface to a small virtio device model. Read each vendor's current security page and ask what they commit to in writing rather than in a launch blog.
Are microVMs too slow to start for a per-request agent loop?
Cold-booting a VM is slow; restoring a pre-baked snapshot is not, and those are different operations. PandaStack keeps no warm pool of idle VMs — every create restores a baked Firecracker snapshot through pre-allocated networking, giving a p50 of 179ms end to end and a p99 around 203ms, with the restore step itself roughly 49ms. The only roughly 3-second boot is the first spawn of a template, before its snapshot has been baked. Forking a running sandbox with copy-on-write memory and a reflinked rootfs is 400-750ms on the same host and 1.2-3.5s cross-host, because the memory image has to travel.
Can I use Modal and a sandbox API together?
Yes, and it's a sensible architecture rather than a compromise. Split by trust level: Modal or a similar serverless platform handles GPU inference, scheduled batch jobs, and heavy Python fan-out over code you wrote and reviewed; a microVM sandbox API handles the untrusted tier — commands and code your model generated, one task per machine, behind a hardware isolation boundary. The two aren't competing for the same request, so splitting them usually costs less and fails more predictably than stretching one platform across both jobs. The same reasoning applies to Vercel Sandbox if your product already deploys there and you only need the sandbox tier for long-running or tightly egress-controlled work.
49ms p50 cold start. Fork, snapshot, and scale to zero.