V2 Sandboxes
Modal’s next-generation Sandbox backend has a number of advantages over the existing backend:
- Create sandboxes with higher throughput
- Lower time-to-interactive
- Run more sandboxes concurrently
Migrating from v1
Available starting in modal 1.5.4, set MODAL_SANDBOX_V2=1 and keep using the standard Sandbox.create() (as well as Sandbox.from_name() and Sandbox.list()). The interface is exactly the same.
Until the upcoming 0.11.0 release, JavaScript and Go still opt in through experimentalCreate / ExperimentalCreate (and experimentalFromName / ExperimentalFromName to look up a named V2 Sandbox).
All of the core Sandbox operations — exec, wait, poll, terminate,
reading stdout/stderr, and writing to stdin — work the same way as
they do in the standard Sandbox API:
import os
os.environ["MODAL_SANDBOX_V2"] = "1"
sb = modal.Sandbox.create(
"sleep", "300",
app=sb_app,
cpu=2,
memory=4096, # MiB
)
p = sb.exec("python", "-c", "print('hello world')")
print(p.stdout.read())
p.wait()
assert p.returncode == 0
sb.terminate()import os
os.environ["MODAL_SANDBOX_V2"] = "1"
sb = await modal.Sandbox.create.aio(
"sleep", "300",
app=sb_app,
cpu=2,
memory=4096, # MiB
)
p = await sb.exec.aio("python", "-c", "print('hello world')")
print(await p.stdout.read.aio())
await p.wait.aio()
assert p.returncode == 0
await sb.terminate.aio()import { ModalClient } from "modal";
const modal = new ModalClient();
const app = await modal.apps.fromName("my-app", { createIfMissing: true });
const image = modal.images.fromRegistry("python:3.13-slim");
const sb = await modal.sandboxes.experimentalCreate(app, image, {
command: ["sleep", "300"],
cpu: 2,
memoryMiB: 4096,
});
const p = await sb.exec(["python", "-c", "print('hello world')"]);
const output = await p.stdout.readText();
console.log(output);
const exitCode = await p.wait();
console.assert(exitCode === 0);
await sb.terminate();sb, _ := mc.Sandboxes.ExperimentalCreate(ctx, app, image, &modal.SandboxCreateParams{
Command: []string{"sleep", "300"},
CPU: 2,
MemoryMiB: 4096,
})
p, _ := sb.Exec(ctx, []string{"python", "-c", "print('hello world')"}, nil)
stdout, _ := io.ReadAll(p.Stdout)
fmt.Println(string(stdout))
exitCode, _ := p.Wait(ctx, nil)
fmt.Println("exit code:", exitCode)
sb.Terminate(ctx, nil)Unsupported features
| Feature | Notes |
|---|---|
| GPUs | GPU Sandboxes must route to v1. Python SDK >=1.5.4 does so automatically. |
| Network file systems | NFS support is unsupported in v2 and will soon be removed platform-wide. |
| Legacy filesystem methods | open, ls, mkdir, rm, and watch do not work on V2. Use Sandbox.filesystem instead. |
Naming a Sandbox after creation
If you created a Sandbox without a name, you can assign one later once with sb._experimental_set_name("my-sandbox") (sb.experimentalSetName(...) in
JavaScript, sb.ExperimentalSetName(...) in Go), then resolve it with Sandbox.from_name as usual.
This is a feature only supported on v2.
Scheduling behavior
When
Sandbox.create()returns on the V2 backend, the sandbox is already scheduled onto a machine (though not yet ready). This differs from v1 semantics, wherecreate()returns before the sandbox is scheduled. As such, once thecreate()call returns, you can expect a shorter, more consistentwait_until_ready:import os os.environ["MODAL_SANDBOX_V2"] = "1" sb = modal.Sandbox.create(app=app, readiness_probe=some_probe) sb.wait_until_ready()You can read more about the sandbox lifecycle in the Sandbox documentation.
If you hit a rough edge that isn’t listed here, please reach out via Slack or email us at support@modal.com.