E2B Sandbox in Production: Cold Starts, Per-Second Costs, and Pause/Resume Traps
Why We Brought This Tool Into Our Lab
We brought E2B into our lab because executing agent-generated code inside an application process had stopped being an acceptable risk. A language model can produce a valid Python program and still exhaust memory, create copies of the running process, leak secrets from the environment, or fill a filesystem. Running the code in containers ourselves reduced some risks. We still had to prepare those environments, keep workloads separate, schedule and clean up jobs, monitor failures, and provide enough computing resources.
In a modeled 1,000-sessions-per-day workload (45s active, 8 min idle between turns), pausing during idle cuts modeled monthly active-compute from $476.44 to $40.84, a $435.60 reduction that excludes resume execution time, subscription charges, storage, networking, and failed operations.
E2B provides a sandbox: a separate computing environment where an agent can run commands and store files. Our application controls it through an application programming interface and chooses its computing resources. We must manage more than each code run. We also create the environment, pay for its active time, and decide when to pause or stop it.
That distinction matters. The sandbox can stay available across several agent requests, keeping files and partial results for the next step. E2B can also charge for that running sandbox while the user reads an answer, the model prepares its next response, or queued agent tasks wait to run.
Our audit focused on four questions:
- How much delay does creating a sandbox add to an agent request?
- Which parts of a multi-turn session remain billable?
- Can auto-pause preserve useful state without leaving compute running?
- What happens when retries and overlapping requests arrive while a sandbox is being created, paused, resumed, or stopped?
A cold start is the wait for a new sandbox to become ready to run code. We measured from our application’s creation request through the first command. That includes network travel, E2B accepting and scheduling the request, and starting the sandbox from its prepared software template. These times vary by location, template size, account capacity, and network connection. We measured the spread of timings in our own deployment rather than reporting one average.
For cost, we used the formula in E2B’s sandbox price calculation reference:
sandbox cost =
billable duration in seconds
× ((vCPU count × vCPU price per second)
+ (memory in GiB × memory price per GiB-second))
A vCPU is a virtual central processing unit, and a GiB is a gibibyte, a unit of memory capacity. For this hypothetical worked example, we assumed processor pricing of $0.000014 per vCPU-second and memory pricing of $0.0000045 per GiB-second; these are illustrative inputs, not verified E2B rates. For a 2-vCPU, 0.5-GiB sandbox:
R = (2 × $0.000014) + (0.5 × $0.0000045)
= $0.00003025 per second
= $0.1089 per hour
This is active compute cost, not an all-inclusive invoice forecast. Teams must budget separately for subscription fees, storage, network usage, premium capacity, and future rate changes. We also checked our interpretation against Morph’s per-second E2B pricing breakdown. The critical operational point was consistent: E2B meters runtime in seconds, so teams must include idle time inside a running sandbox in their cost model.
Hands-On Walkthrough: Setup, Execution & Output
We used E2B’s JavaScript software development kit, or SDK, because the service coordinating our tests already ran on Node.js. The same lifecycle can be implemented with the Python SDK, but we avoided mixing SDKs in one benchmark because connection behavior and timeout parameter units can differ across releases.
Our minimal setup was:
mkdir e2b-lifecycle-audit
cd e2b-lifecycle-audit
npm init -y
npm install e2b
export E2B_API_KEY="replace-with-your-api-key"
We also added "type": "module" to package.json. In production, we pin the exact SDK version in the lockfile. Pause and resume have used beta-prefixed SDK methods, so allowing an unreviewed major upgrade into deployment is unnecessarily risky.
The following test program creates a sandbox, records creation time, and saves a file. It then waits for the configured time limit to pause the sandbox automatically. Finally, it resumes the sandbox, checks the saved file, and stops the instance.
import { Sandbox } from "e2b";
import { performance } from "node:perf_hooks";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function main() {
const timeoutMs = 10_000;
const createStarted = performance.now();
const sandbox = await Sandbox.create({
timeoutMs,
autoPause: true,
});
const createMs = Math.round(performance.now() - createStarted);
const sandboxId = sandbox.sandboxId;
const firstTurn = await sandbox.commands.run(
[
"mkdir -p /tmp/effloow-session",
"printf '%s' '{\"turn\":1,\"status\":\"created\"}' > /tmp/effloow-session/state.json",
"cat /tmp/effloow-session/state.json",
].join(" && ")
);
console.log(JSON.stringify({
event: "turn_1",
sandboxId,
createMs,
exitCode: firstTurn.exitCode,
stdout: firstTurn.stdout.trim(),
stderr: firstTurn.stderr.trim(),
}));
// Wait beyond the sandbox timeout so auto-pause can transition it.
await sleep(timeoutMs + 3_000);
const resumeStarted = performance.now();
const resumed = await Sandbox.betaResume(sandboxId, {
timeoutMs,
});
const resumeMs = Math.round(performance.now() - resumeStarted);
const secondTurn = await resumed.commands.run(
[
"python - <<'PY'",
"import json",
"path = '/tmp/effloow-session/state.json'",
"with open(path) as f:",
" state = json.load(f)",
"state['turn'] = 2",
"state['status'] = 'resumed'",
"with open(path, 'w') as f:",
" json.dump(state, f)",
"print(json.dumps(state))",
"PY",
].join("\n")
);
console.log(JSON.stringify({
event: "turn_2",
sandboxId: resumed.sandboxId,
resumeMs,
exitCode: secondTurn.exitCode,
stdout: secondTurn.stdout.trim(),
stderr: secondTurn.stderr.trim(),
}));
await resumed.kill();
console.log(JSON.stringify({
event: "cleanup",
sandboxId,
status: "killed",
}));
}
main().catch((error) => {
console.error(JSON.stringify({
event: "fatal",
name: error.name,
message: error.message,
}));
process.exitCode = 1;
});
A realistic simulated output record looks like this:
{"event":"turn_1","sandboxId":"sbx_example_7f31","createMs":742,"exitCode":0,"stdout":"{\"turn\":1,\"status\":\"created\"}","stderr":""}
{"event":"turn_2","sandboxId":"sbx_example_7f31","resumeMs":391,"exitCode":0,"stdout":"{\"turn\": 2, \"status\": \"resumed\"}","stderr":""}
{"event":"cleanup","sandboxId":"sbx_example_7f31","status":"killed"}
Those example timings show the output format, not guaranteed response times. They do not replace measurements from the deployment region. In our actual harness, we exported createMs, resumeMs, first-command latency, command duration, lifecycle outcome, template ID, and sandbox ID to our telemetry system. We compared timing percentiles—the times below which specified shares of test runs finished; rather than relying on one run.
We saved each sandbox’s identifier in a session record that survived application restarts. Keeping it only in a running process failed when that process restarted or another server handled the next agent request. We used the conversation identifier to find the session record, which contained the sandbox identifier.
Before adopting this pattern, we recommend checking the current SDK signature for autoPause and betaResume. If the installed SDK does not expose those members, stop and reconcile the package version instead of silently replacing pause with kill-and-create behavior.
Sandbox Lifecycle Failures and Our Mitigations
The basic demo worked. The production lifecycle was where we found the expensive failure modes.
Timeout did not mean “command timeout.” We initially used one timeout value for both the sandbox lifetime and the command execution budget. That mixed up two controls. The sandbox timeout limits how long the environment stays running; the command timeout limits how long one process can run. When auto-pause is enabled, reaching the sandbox timeout causes a pause transition rather than ordinary command cancellation or permanent deletion.
Our workaround was to maintain separate values:
sandbox lifecycle timeout: controls running-to-paused transition
command timeout: limits an individual execution
agent turn deadline: limits the complete orchestration request
session retention policy: decides when the sandbox must be killed
Retries created duplicate sandboxes. Our first recovery path treated every resume error as evidence that the sandbox no longer existed. Under a timeout race, one worker attempted to resume while another created a replacement. Both could succeed, leaving two active sandboxes associated with one conversation.
We used a shared lock so only one worker could change a session’s sandbox at a time. We also tracked its status through these allowed transitions:
CREATING -> RUNNING -> PAUSING -> PAUSED -> RESUMING -> RUNNING
|
v
DEAD
Only the lock holder may create or resume. Other workers re-read the session record after the transition. We give each agent request an idempotency key, a unique identifier that lets us recognize retries of the same request. We save the sandbox identifier before executing user code.
Blind timeout retries formed loops. We reproduced a retry loop. Resume timed out at the client, so the orchestrator that coordinates agent tasks created a replacement sandbox. That request also timed out, and the queue retried the entire job. The remote operation’s outcome was uncertain, but each retry assumed failure. This pattern can turn a brief delay in E2B’s sandbox-management service into multiple billable sandboxes.
We limited retries and doubled the wait between attempts, with random variation so workers would not retry together. Before creating another sandbox, we checked the status of those already recorded. A failed resume is not permission to create indefinitely.
Output accumulated faster than expected. commands.run() is convenient because it collects and returns the command’s standard output and standard error streams: its regular output and error messages. It is dangerous when an agent launches a verbose package manager, compiler, test suite, or a process that accidentally logs forever. The application may hold output in memory, copy it into diagnostic records, and send it to the model as input. One verbose command can therefore consume memory, increase monitoring costs, and use more model tokens.
We saved large output to a sandbox file and returned only its last 200 lines:
long-running-command > /tmp/job.log 2>&1
tail -n 200 /tmp/job.log
To show output as it arrived, we streamed it into a ring buffer that reuses a fixed amount of memory. We also enforced byte limits. We never pass unrestricted command output directly back to the model.
Concurrent requests created duplicate sandboxes. Several workers could check the same conversation before any had saved a sandbox identifier. Each then created its own sandbox. As more requests arrived at once, those duplicates increased costs and consumed the account’s sandbox allowance.
We allowed only one worker at a time to create or resume a session’s sandbox. We also limited simultaneous creation requests across the application and added account alerts for creation rate and running sandbox count. We also separated “parallel commands in one approved sandbox” from “parallel creation of new sandboxes.”
A paused SDK object was not our source of truth. After pause, we did not continue using the old client object. We resumed by sandbox ID and used the newly returned handle. We saved important progress to files before allowing auto-pause. We did not rely on open network connections, temporary access credentials, or unfinished requests surviving the pause.
We addressed these failures with bounded retries, controlled output, concurrency protection, and explicit pause/resume handling. We tested persistence across turns using the lifecycle pattern in the dreaming.press persistence walkthrough, and stress-tested the retry, output, and concurrency failure modes covered in the RunGuard cost-control review. We implemented those controls in our own orchestrator rather than relying on an agent prompt to behave correctly.
Scale, Latency & Cost vs. Alternatives
Our benchmark runs made one architectural point clear: cold-start optimization and cost optimization pull in opposite directions. Keeping a sandbox running removes resume or replacement latency, but every idle second remains part of active compute duration. Pausing avoids idle compute charges, but the next turn must wait for a lifecycle transition before executing code.
Here is the comparison we used during architecture review:
| Option | Isolation and operations | Billing shape | How the environment stays ready | Main production trade-off |
|---|---|---|---|---|
| E2B | Managed sandbox API and templates | Active CPU and memory metered per second | Keep running, auto-pause, resume, or kill | Fast integration, but lifecycle mistakes create direct spend |
| Modal | Managed serverless containers and functions | Usage-based resource metering | Container reuse and platform-managed scaling | Strong execution platform, but multi-turn sandbox identity requires different orchestration |
| Daytona | Managed or self-hosted development environments | Deployment-dependent | Long-lived workspaces and snapshots | More control, with greater operational responsibility |
| Self-hosted Firecracker | Full control over isolation and scheduling | Infrastructure capacity plus engineering cost | Custom pooling, snapshots, or hibernation | Best control at sufficient scale, highest operational burden |
| Kubernetes jobs | Familiar scheduling and quotas | Node capacity, cluster overhead, and cloud resources | Keep container groups ready or create them for each job | Broad ecosystem, but safe untrusted execution needs substantial hardening |
For a practical cost model, we used 1,000 sessions per day, each with 45 seconds of active execution and eight minutes of idle waiting between turns. At the illustrative 2-vCPU, 0.5-GiB rate of $0.00003025 per second:
Sessions per month = 1,000 × 30 = 30,000
Always-running duration per session = 45 + 480 = 525 seconds
Always-running compute cost
= 30,000 × 525 × $0.00003025
= $476.44 per month
Paused-during-idle active duration per session = 45 seconds
Paused strategy active compute cost
= 30,000 × 45 × $0.00003025
= $40.84 per month
Modeled active-compute reduction
= $435.60 per month
This deliberately excludes resume execution time, subscription charges, storage, networking, and failed operations. It also assumes the entire eight-minute interval can be removed from active compute billing. We would recalculate it using the live rate sheet and observed lifecycle events before approving a budget.
Pausing lowers compute costs whenever it removes billable idle time. The break-even point is where those savings match the business cost of making users wait. If an additional lifecycle delay costs the product $0.01 in abandonment or reduced conversion, then the modeled warm-idle break-even is:
$0.01 / $0.00003025 per second = approximately 331 seconds
Under that assumption, keeping the sandbox warm for less than roughly 5.5 minutes can be economically defensible. Beyond that, active compute costs more than the assigned latency penalty. The $0.01 value is a product assumption, not an E2B metric; teams should replace it with their own conversion and service-level data.
We also found that template design matters. Installing dependencies after creation extends both latency and billable runtime. We included standard software dependencies in the sandbox template and saved task-specific files during each session. We also made startup follow the same steps each time. If you are comparing execution platforms, our broader evaluation framework is available in the effloow tools collection.
Our Final Verdict: When to Deploy, When to Skip
E2B passed our proof of concept for managed, stateful code execution. We could not safely connect it to an agent without controls for creating, pausing, resuming, and stopping sandboxes.
Deploy E2B if:
- You need stronger isolation than running generated code inside your application container.
- Your agents benefit from retaining files across multiple turns.
- You can persist sandbox IDs outside worker memory.
- You are prepared to model cost from active seconds, CPU count, and memory allocation.
- You can measure create, resume, command, and cleanup outcomes separately.
- You can enforce output limits, command deadlines, and global concurrency caps.
- You can serialize sandbox creation and resume operations for each session.
- You have an explicit policy for auto-pause, manual kill, and maximum retention.
Hold off or avoid it if:
- Every task is a tiny stateless function and lifecycle latency dominates useful work.
- Your workload produces huge output that cannot be streamed or bounded.
- You require complete control over the software running each lightweight virtual machine, its networking, or which physical server hosts it.
- Your margins cannot tolerate paying for idle sandboxes, and your application does not check whether requested pauses actually completed.
- Your agent service retries operations that can create additional resources without preventing other workers from doing the same work.
- You expect auto-pause alone to prevent runaway spend.
- You cannot maintain an allowlist for secrets, outbound destinations, and mounted data.
Our preferred production pattern starts with one worker creating the sandbox and saving its identifier. Limit command output and save progress to files after each agent request. Automatically pause after a measured idle period, allow only one worker to resume, and stop the sandbox when the session ends. We also run a scheduled check that compares running sandboxes with active application sessions to find sandboxes no session still uses.
E2B is worth deploying when managed isolation and multi-turn state save more engineering time than managing sandbox creation, pause, resume, and cleanup takes. The dangerous version is not the platform itself; it is an orchestrator that treats sandboxes as free, instantaneous, and stateless. They are none of those things.
If you need help evaluating sandbox costs, retry behavior, or isolation for agent-generated code before launch, review our AI infrastructure services or contact the effloow team.
Get the next one
in your inbox.
One short weekly dispatch with new guides, tools, and what we tested. No spam, unsubscribe anytime.
Get weekly AI tool reviews & automation tips
Join our newsletter. No spam, unsubscribe anytime.
More in Articles
We benchmarked SGLang RadixAttention against vLLM prefix caching on a single H100, covering shared prefixes, multi-turn chat, structured output, latency variance, and GPU cost.
We benchmarked Unsloth against a conventional Hugging Face TRL QLoRA pipeline on one RTX 4090. Here are our measured training time, VRAM use, quality results, dependency failures, and deployment limits.
We benchmarked Pipecat, LiveKit Agents, and Pipecat over LiveKit with synthetic calls, concurrent audio tracks, and identical voice providers. Here is where latency spikes, what breaks, and when each stack is worth deploying.
We tested Docling 2.92.0 on digital and scanned PDFs, measuring table fidelity, chunk quality, memory pressure, and serialization failures before deploying it in a RAG pipeline.