Sandboxes
This page is a high-level guide to Sandboxes, secure containers for executing untrusted user or agent code on Modal.
For reference documentation on the modal.Sandbox interface,
see this page.
What are Sandboxes and why should I use them?
In addition to the Function interface, Modal has a direct interface for defining containers at runtime and securely running arbitrary code inside them.
This can be useful if, for example, you want to:
- Execute code generated by a language model.
- Create isolated environments for running untrusted code.
- Check out a git repository and run a command against it, like a test suite, or
npm lint. - Run containers with arbitrary dependencies and setup scripts.
Each individual job is called a Sandbox and can be created using the Sandbox.create constructor:
sb_app = modal.App.lookup("my-app", create_if_missing=True)
sb = modal.Sandbox.create(app=sb_app)
p = sb.exec("python", "-c", "print('hello')", timeout=3)
print(p.stdout.read())
p = sb.exec("bash", "-c", "for i in {1..10}; do date +%T; sleep 0.5; done", timeout=5)
for line in p.stdout:
# Avoid double newlines by using end="".
print(line, end="")
sb.terminate()
sb.detach()sb_app = await modal.App.lookup.aio("my-app", create_if_missing=True)
sb = await modal.Sandbox.create.aio(app=sb_app)
p = await sb.exec.aio("python", "-c", "print('hello')", timeout=3)
print(await p.stdout.read.aio())
p = await sb.exec.aio("bash", "-c", "for i in {1..10}; do date +%T; sleep 0.5; done", timeout=5)
async for line in p.stdout:
# Avoid double newlines by using end="".
print(line, end="")
await sb.terminate.aio()
await sb.detach.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.create(app, image);
const p = await sb.exec(["python", "-c", "print('hello')"], {
timeoutMs: 3 * 1000,
});
console.log(await p.stdout.readText());
const p2 = await sb.exec(
["bash", "-c", "for i in {1..10}; do date +%T; sleep 0.5; done"],
{ timeoutMs: 5 * 1000 },
);
for await (const line of p2.stdout) {
process.stdout.write(line);
}
await sb.terminate();package main
import (
"context"
"fmt"
"io"
"os"
"time"
modal "github.com/modal-labs/modal-client/go"
)
func main() {
ctx := context.Background()
mc, _ := modal.NewClient()
app, _ := mc.Apps.FromName(ctx, "my-app", &modal.AppFromNameParams{
CreateIfMissing: true,
})
image := mc.Images.FromRegistry("python:3.13-slim", nil)
sb, _ := mc.Sandboxes.Create(ctx, app, image, nil)
defer sb.Terminate(ctx, nil)
p, _ := sb.Exec(ctx, []string{"python", "-c", "print('hello')"}, &modal.SandboxExecParams{
Timeout: 3 * time.Second,
})
stdout, _ := io.ReadAll(p.Stdout)
fmt.Println(string(stdout))
p2, _ := sb.Exec(ctx, []string{"bash", "-c", "for i in {1..10}; do date +%T; sleep 0.5; done"}, &modal.SandboxExecParams{
Timeout: 5 * time.Second,
})
io.Copy(os.Stdout, p2.Stdout)
}Note: you can run the above example as a script directly with python my_script.py. modal run is not needed here since there is no entrypoint.
Sandboxes require an App to be passed when spawned from outside
of a Modal container. You may pass in a regular App object or look one up by name with App.lookup. The create_if_missing flag on App.lookup will create an App with the given name if it doesn’t exist.
Lifecycle
Events
Every Sandbox moves through a series of lifecycle events as it progresses from creation to completion. Understanding these events is useful for monitoring, debugging, and building automations that react to Sandbox state changes.
The lifecycle events, in order, are:
Created — The Sandbox has been requested and registered with Modal. At this point the Sandbox object exists and has an ID, but no compute resources have been allocated yet. This is the initial state immediately after calling
Sandbox.create.Scheduled — The Sandbox has been scheduled to a specific worker. The worker is now provisioning the resources the Sandbox needs (CPU, memory, GPU, volumes, etc.) and preparing the container environment. The Sandbox will transition to Started once the container is fully initialized.
Started — The Sandbox’s container has been launched on a worker and the entrypoint process (if any) is running. At this point you can begin executing commands inside the Sandbox with
sandbox.exec(...). Network tunnels and volume mounts are active.Ready — If readiness probes are enabled for the Sandbox, this event fires once the probe succeeds, indicating that the service inside the Sandbox is fully initialized and ready to accept traffic. This is especially useful for Sandboxes running web servers or other services that need warm-up time before they can handle requests. If readiness probes are not configured, this event is skipped.
Finished — The Sandbox has stopped running. This can happen for several reasons: the entrypoint process exited on its own, the Sandbox was explicitly terminated (via the dashboard or
sandbox.terminate()), the timeout or idle timeout was reached, or an out-of-memory condition occurred. Once finished, no further commands can be executed inside the Sandbox. You can learn more about why a Sandbox stopped running in the dashboard or by examining the exit code returned fromsandbox.poll().
Timeouts
Sandboxes have a default maximum lifetime of 5 minutes. You can change this by passing
a timeout of up to 24 hours to the Sandbox.create(...) function.
sb = modal.Sandbox.create(app=sb_app, timeout=10*60) # 10 minutes
sb.detach()sb = await modal.Sandbox.create.aio(app=sb_app, timeout=10*60) # 10 minutes
await sb.detach.aio()const sb = await modal.sandboxes.create(app, image, {
timeoutMs: 10 * 60 * 1000, // 10 minutes
});
sb.detach();sb, err := mc.Sandboxes.Create(ctx, app, image, &modal.SandboxCreateParams{
Timeout: 10 * time.Minute,
})
defer sb.Detach()If you need a Sandbox to run for more than 24 hours, we recommend using Filesystem Snapshots to preserve its state, and then restore from that snapshot with a subsequent Sandbox.
Idle Timeouts
Sandboxes can also be automatically terminated after a period of inactivity - you can do this by setting the idle_timeout parameter. A Sandbox is considered active if any of the following are true:
- It has an active command running (via
sb.exec(...)) - Its stdin is being written to (via
sb.stdin.write()) - It has an open TCP connection over one of its Tunnels
Readiness Probes (Beta)
After a Sandbox starts, you often need to run custom initialization logic before it’s
ready for use — pulling code with git pull, installing dependencies, starting a server,
writing config files, or other setup that isn’t baked into the image. Readiness probes
give you a way to track when that initialization is complete, so you don’t have to build
the polling or signaling yourself. Modal also uses probe results to give you
observability into how long this startup phase typically takes.
A readiness probe is a check that Modal runs automatically inside the Sandbox at a
configurable interval. You can then call wait_until_ready() to block until the probe
succeeds.
There are two types of readiness probes:
- TCP probe — Checks whether a TCP port inside the Sandbox is accepting connections. This is the most common choice when your startup logic includes launching a server.
- Exec probe — Runs an arbitrary command inside the Sandbox and succeeds when the command exits with status code 0. Use this for any other readiness condition: checking that a file exists, verifying a setup script has completed, confirming dependencies are installed, etc.
Both probe types accept an interval_ms parameter (default: 100ms) that controls how
frequently the check is retried until it succeeds.
TCP readiness probe
Use a TCP probe when your Sandbox starts a server and you want to wait until it’s listening on a port:
sb = modal.Sandbox.create(
"python3", "-m", "http.server", "8080",
readiness_probe=modal.Probe.with_tcp(8080),
app=sb_app,
)
# Blocks until port 8080 is accepting connections
sb.wait_until_ready()
# The server is now ready — interact with it via tunnels, exec, etc.
sb.terminate()
sb.detach()sb = await modal.Sandbox.create.aio(
"python3", "-m", "http.server", "8080",
readiness_probe=modal.Probe.with_tcp(8080),
app=sb_app,
)
# Blocks until port 8080 is accepting connections
await sb.wait_until_ready.aio()
# The server is now ready — interact with it via tunnels, exec, etc.
await sb.terminate.aio()
await sb.detach.aio()import { ModalClient, Probe } 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.create(app, image, {
entrypoint: ["python3", "-m", "http.server", "8080"],
readinessProbe: Probe.withTcp(8080),
});
// Blocks until port 8080 is accepting connections
await sb.waitUntilReady();
// The server is now ready — interact with it via tunnels, exec, etc.
await sb.terminate();package main
import (
"context"
"time"
modal "github.com/modal-labs/modal-client/go"
)
func main() {
ctx := context.Background()
mc, _ := modal.NewClient()
app, _ := mc.Apps.FromName(ctx, "my-app", &modal.AppFromNameParams{
CreateIfMissing: true,
})
image := mc.Images.FromRegistry("python:3.13-slim", nil)
probe, _ := modal.NewTCPProbe(8080, nil)
sb, _ := mc.Sandboxes.Create(ctx, app, image, &modal.SandboxCreateParams{
Entrypoint: []string{"python3", "-m", "http.server", "8080"},
ReadinessProbe: probe,
})
defer sb.Detach()
// Blocks until port 8080 is accepting connections
sb.WaitUntilReady(ctx, 5*time.Minute)
// The server is now ready — interact with it via tunnels, exec, etc.
sb.Terminate(ctx, nil)
}Exec readiness probe
Use an exec probe when readiness depends on something other than a TCP port — for example, waiting for a file to be created or a setup script to complete:
sb = modal.Sandbox.create(
"bash", "-c", "sleep 5 && touch /tmp/ready && sleep 3600",
readiness_probe=modal.Probe.with_exec(
"sh", "-c", "test -f /tmp/ready",
interval_ms=250,
),
app=sb_app,
)
# Blocks until "test -f /tmp/ready" exits with code 0
sb.wait_until_ready()
# The sandbox is now ready
p = sb.exec("cat", "/tmp/ready")
sb.terminate()
sb.detach()sb = await modal.Sandbox.create.aio(
"bash", "-c", "sleep 5 && touch /tmp/ready && sleep 3600",
readiness_probe=modal.Probe.with_exec(
"sh", "-c", "test -f /tmp/ready",
interval_ms=250,
),
app=sb_app,
)
# Blocks until "test -f /tmp/ready" exits with code 0
await sb.wait_until_ready.aio()
# The sandbox is now ready
p = await sb.exec.aio("cat", "/tmp/ready")
await sb.terminate.aio()
await sb.detach.aio()import { ModalClient, Probe } 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.create(app, image, {
entrypoint: ["bash", "-c", "sleep 5 && touch /tmp/ready && sleep 3600"],
readinessProbe: Probe.withExec(["sh", "-c", "test -f /tmp/ready"], {
intervalMs: 250,
}),
});
// Blocks until "test -f /tmp/ready" exits with code 0
await sb.waitUntilReady();
// The sandbox is now ready
await sb.terminate();package main
import (
"context"
"time"
modal "github.com/modal-labs/modal-client/go"
)
func main() {
ctx := context.Background()
mc, _ := modal.NewClient()
app, _ := mc.Apps.FromName(ctx, "my-app", &modal.AppFromNameParams{
CreateIfMissing: true,
})
image := mc.Images.FromRegistry("python:3.13-slim", nil)
probe, _ := modal.NewExecProbe(
[]string{"sh", "-c", "test -f /tmp/ready"},
&modal.ExecProbeParams{Interval: 250 * time.Millisecond},
)
sb, _ := mc.Sandboxes.Create(ctx, app, image, &modal.SandboxCreateParams{
Entrypoint: []string{"bash", "-c", "sleep 5 && touch /tmp/ready && sleep 3600"},
ReadinessProbe: probe,
})
defer sb.Detach()
// Blocks until "test -f /tmp/ready" exits with code 0
sb.WaitUntilReady(ctx, 5*time.Minute)
// The sandbox is now ready
sb.Terminate(ctx, nil)
}Note: Readiness probes will run for a maximum of 5 minutes. If the probe does not
succeed within that window, wait_until_ready() will raise a TimeoutError. The probe
timeout does not automatically terminate the Sandbox — you may want to catch the TimeoutError and explicitly terminate the Sandbox if readiness is never achieved:
try:
sb.wait_until_ready()
except TimeoutError:
print("Sandbox failed to become ready")
sb.terminate()
sb.detach()try:
await sb.wait_until_ready.aio()
except TimeoutError:
print("Sandbox failed to become ready")
await sb.terminate.aio()
await sb.detach.aio()try {
await sb.waitUntilReady();
} catch (err) {
console.log("Sandbox failed to become ready");
await sb.terminate();
}if err := sb.WaitUntilReady(ctx, 5*time.Minute); err != nil {
fmt.Println("Sandbox failed to become ready")
sb.Terminate(ctx, nil)
}If you call wait_until_ready() on a Sandbox that was not configured with a readiness
probe, an error will be raised. Similarly, calling it after the Sandbox has been
terminated will raise an error. However, calling wait_until_ready() after the Sandbox
has already become ready returns immediately.
Return Codes
Unix-style exit codes are provided to help diagnose conditions such as success, manual termination, or out-of-memory.
They are available on both:
- Processes in the sandbox (via
ContainerProcess.returncode/ContainerProcess.poll()) - The Sandbox itself (via
Sandbox.returncode/Sandbox.poll())
sb = modal.Sandbox.create(app=sb_app)
# Read returncode of individual process
p = sb.exec("sh", "-c", "exit 42")
p.wait()
print(p.returncode) # 42
# Read returncode of finished sandbox
# Terminate sends a SIGKILL, code 137
sb.terminate(wait=True)
print(sb.returncode) # 137sb = await modal.Sandbox.create.aio(app=sb_app)
# Read returncode of individual process
p = await sb.exec.aio("sh", "-c", "exit 42")
await p.wait.aio()
print(p.returncode) # 42
# Read returncode of finished sandbox
# Terminate sends a SIGKILL, code 137
await sb.terminate.aio(wait=True)
print(sb.returncode) # 137const sb = await modal.sandboxes.create(app, image);
// Read returncode of individual process
const p = await sb.exec(["sh", "-c", "exit 42"]);
const returnCode = await p.wait();
console.log(returnCode); // 42
// Read returncode of finished sandbox
// Terminate sends a SIGKILL, code 137
const returnCodeSb = await sb.terminate({ wait: true });
console.log(returnCodeSb); // 137sb, _ := mc.Sandboxes.Create(ctx, app, image, nil)
// Read returncode of individual process
p, _ := sb.Exec(ctx, []string{"sh", "-c", "exit 42"}, nil)
returnCode, _ := p.Wait(ctx)
fmt.Println(returnCode) // 42
// Read returncode of finished sandbox
// Terminate sends a SIGKILL, code 137
returnCodeSb, _ := sb.Terminate(ctx, &modal.SandboxTerminateParams{Wait: true})
fmt.Println(returnCodeSb) // 137Configuration
Sandboxes support nearly all configuration options found in regular modal.Functions.
Refer to Sandbox.create for further documentation
on Sandbox configs.
For example, Images and Volumes can be used just as with functions:
sb = modal.Sandbox.create(
image=modal.Image.debian_slim().pip_install("pandas"),
volumes={"/data": modal.Volume.from_name("data-volume", create_if_missing=True)},
app=sb_app,
)
sb.detach()sb = await modal.Sandbox.create.aio(
image=modal.Image.debian_slim().pip_install("pandas"),
volumes={"/data": modal.Volume.from_name("data-volume", create_if_missing=True)},
app=sb_app,
)
await sb.detach.aio()const image = modal.images.fromRegistry("python:3.13-slim");
const volume = modal.volumes.fromName("my-volume");
const sb = await modal.sandboxes.create(app, image, {
volumes: { "/data": volume },
workdir: "/repo",
});
sb.detach();image := mc.Images.FromRegistry("python:3.13-slim", nil)
volume := mc.Volumes.FromName("my-volume", nil)
sb, err := mc.Sandboxes.Create(ctx, app, image, &modal.SandboxCreateParams{
Volumes: map[string]*modal.Volume{"/data": volume},
Workdir: "/repo",
})
defer sb.Detach()Environments
Environment variables
You can set environment variables using inline secrets:
secret = modal.Secret.from_dict({"MY_SECRET": "hello"})
sb = modal.Sandbox.create(
secrets=[secret],
app=sb_app,
)
p = sb.exec("bash", "-c", "echo $MY_SECRET")
print(p.stdout.read())
sb.detach()secret = modal.Secret.from_dict({"MY_SECRET": "hello"})
sb = await modal.Sandbox.create.aio(
secrets=[secret],
app=sb_app,
)
p = await sb.exec.aio("bash", "-c", "echo $MY_SECRET")
print(await p.stdout.read.aio())
await sb.detach.aio()const secret = modal.secrets.fromObject({ MY_SECRET: "hello" });
const image = modal.images.fromRegistry("python:3.13-slim");
const sb = await modal.sandboxes.create(app, image, {
secrets: [secret],
});
const p = await sb.exec(["bash", "-c", "echo $MY_SECRET"]);
console.log(await p.stdout.readText());
sb.detach();secret, err := mc.Secrets.FromMap(ctx, map[string]string{"MY_SECRET": "hello"}, nil)
image := mc.Images.FromRegistry("python:3.13-slim", nil)
sb, err := mc.Sandboxes.Create(ctx, app, image, &modal.SandboxCreateParams{
Secrets: []*modal.Secret{secret},
})
defer sb.Detach()
p, err := sb.Exec(ctx, []string{"bash", "-c", "echo $MY_SECRET"}, nil)
stdout, err := io.ReadAll(p.Stdout)
fmt.Println(string(stdout))Custom Images
Sandboxes support custom images just as Functions do. However, while you’ll typically
invoke a Modal Function with the modal run cli, you typically spawn a Sandbox
with a simple script call. As such, you may need to manually enable output streaming
to see your image build logs:
image = modal.Image.debian_slim().pip_install("pandas", "numpy")
with modal.enable_output():
sb = modal.Sandbox.create(image=image, app=sb_app)
sb.detach()image = modal.Image.debian_slim().pip_install("pandas", "numpy")
with modal.enable_output():
sb = await modal.Sandbox.create.aio(image=image, app=sb_app)
await sb.detach.aio()const image = modal.images
.fromRegistry("python:3.13-slim")
.dockerfileCommands(["RUN pip install pandas numpy"]);
const sb = await modal.sandboxes.create(app, image);
sb.detach();image := mc.Images.FromRegistry("python:3.13-slim", nil).
DockerfileCommands([]string{"RUN pip install pandas numpy"}, nil)
// Note: Image build logs are automatically streamed in Go
sb, err := mc.Sandboxes.Create(ctx, app, image, nil)
defer sb.Detach()Running a Sandbox with an entrypoint
In most cases, Sandboxes are treated as a generic container that can run arbitrary commands. However, in some cases, you may want to run a single command or script as the entrypoint of the Sandbox. You can do this by passing command arguments to the Sandbox constructor:
sb = modal.Sandbox.create("python", "-m", "http.server", "8080", app=sb_app, timeout=10)
for line in sb.stdout:
print(line, end="")
sb.detach()sb = await modal.Sandbox.create.aio("python", "-m", "http.server", "8080", app=sb_app, timeout=10)
async for line in sb.stdout:
print(line, end="")
await sb.detach.aio()const sb = await modal.sandboxes.create(app, image, {
entrypoint: ["python", "-m", "http.server", "8080"],
timeoutMs: 10 * 1000,
});
sb.detach();sb, err := mc.Sandboxes.Create(ctx, app, image, &modal.SandboxCreateParams{
Entrypoint: []string{"python", "-m", "http.server", "8080"},
Timeout: 10 * time.Second,
})
sb.Detach()This functionality is most useful for running long-lived services that you want to keep running in the background. See our Jupyter notebook example for a more concrete example of this.
Referencing Sandboxes from other code
If you have a running Sandbox, you can retrieve it using the from_id method.
sb = modal.Sandbox.create(app=sb_app)
sb_id = sb.object_id
sb.detach()
# ... later in the program ...
sb2 = modal.Sandbox.from_id(sb_id)
p = sb2.exec("echo", "hello")
print(p.stdout.read())
sb2.terminate()
sb2.detach()sb = await modal.Sandbox.create.aio(app=sb_app)
sb_id = sb.object_id
await sb.detach.aio()
# ... later in the program ...
sb2 = await modal.Sandbox.from_id.aio(sb_id)
p = await sb2.exec.aio("echo", "hello")
print(await p.stdout.read.aio())
await sb2.terminate.aio()
await sb2.detach.aio()const sb = await modal.sandboxes.create(app, image);
const sbId = sb.sandboxId;
await sb.detach();
// ... later in the program ...
const sb2 = await modal.sandboxes.fromId(sbId);
const p = await sb2.exec(["echo", "hello"]);
console.log(await p.stdout.readText());
await sb2.terminate();sb, err := mc.Sandboxes.Create(ctx, app, image, nil)
defer sb.Detach()
sbId := sb.SandboxID
// ... later in the program ...
sb2, err := mc.Sandboxes.FromID(ctx, sbId)
defer sb2.Terminate(ctx, nil)
p, err := sb2.Exec(ctx, []string{"echo", "hello"}, nil)
stdout, err := io.ReadAll(p.Stdout)
fmt.Println(string(stdout))A common use case for this is keeping a pool of Sandboxes available for executing tasks
as they come in. You can keep a list of object_ids of Sandboxes that are “open” and
reuse them, closing over the object_id in whatever function is using them.
Named Sandboxes
You can assign a name to a Sandbox when creating it. Each name must be unique within an app -
only one running Sandbox can use a given name at a time. Note that the associated app must be
a deployed app. Once a Sandbox completely stops running, its name becomes available for reuse.
Some applications find Sandbox Names to be useful for ensuring that no more than one Sandbox is
running per resource or project. If a Sandbox with the given name is already running, create() will raise an error.
sb1 = modal.Sandbox.create(app=sb_app, name="my-name")
# This will raise a modal.exception.AlreadyExistsError.
sb2 = modal.Sandbox.create(app=sb_app, name="my-name")sb1 = await modal.Sandbox.create.aio(app=sb_app, name="my-name")
# This will raise a modal.exception.AlreadyExistsError.
sb2 = await modal.Sandbox.create.aio(app=sb_app, name="my-name")const sb1 = await modal.sandboxes.create(app, image, { name: "my-name" });
// this will raise an AlreadyExistsError
const sb2 = await modal.sandboxes.create(app, image, { name: "my-name" });sb1, err := mc.Sandboxes.Create(ctx, app, image, &modal.SandboxCreateParams{
Name: "my-name",
})
// this will return an error
sb2, err := mc.Sandboxes.Create(ctx, app, image, &modal.SandboxCreateParams{
Name: "my-name",
})A named Sandbox may be fetched from a deployed app using from_name() but only
if the Sandbox is currently running. If no running Sandbox is found, from_name() will raise
an error.
sb_app = modal.App.lookup("my-app", create_if_missing=True)
sb1 = modal.Sandbox.create(app=sb_app, name="my-name")
# Returns the currently running Sandbox with the name "my-name" from the
# deployed app named "my-app".
sb2 = modal.Sandbox.from_name("my-app", "my-name")
assert sb1.object_id == sb2.object_id # sb1 and sb2 refer to the same Sandbox
sb1.detach()
sb2.detach()sb_app = await modal.App.lookup.aio("my-app", create_if_missing=True)
sb1 = await modal.Sandbox.create.aio(app=sb_app, name="my-name")
# Returns the currently running Sandbox with the name "my-name" from the
# deployed app named "my-app".
sb2 = await modal.Sandbox.from_name.aio("my-app", "my-name")
assert sb1.object_id == sb2.object_id # sb1 and sb2 refer to the same Sandbox
await sb1.detach.aio()
await sb2.detach.aio()const app = await modal.apps.fromName("my-app", { createIfMissing: true });
const sb1 = await modal.sandboxes.create(app, image, { name: "my-name" });
// returns the currently running Sandbox with the name "my-name" from the
// deployed app named "my-app".
const sb2 = await modal.sandboxes.fromName("my-app", "my-name");
console.assert(sb1.sandboxId === sb2.sandboxId); // sb1 and sb2 refer to the same Sandbox
sb1.detach();
sb2.detach();app, err := mc.Apps.FromName(ctx, "my-app", &modal.AppFromNameParams{
CreateIfMissing: true,
})
sb1, err := mc.Sandboxes.Create(ctx, app, image, &modal.SandboxCreateParams{
Name: "my-name",
})
// returns the currently running Sandbox with the name "my-name" from the
// deployed app named "my-app".
sb2, err := mc.Sandboxes.FromName(ctx, "my-app", "my-name", nil)
// sb1 and sb2 refer to the same Sandbox
fmt.Println(sb1.SandboxID == sb2.SandboxID)
defer sb1.Detach()
defer sb2.Detach()Sandbox Names may contain only alphanumeric characters, dashes, periods, and underscores, and must be shorter than 64 characters.
Tagging
Sandboxes can also be tagged with arbitrary key-value pairs. These tags can be used
to filter results in Sandbox.list.
sandbox_v1_1 = modal.Sandbox.create("sleep", "10", app=sb_app)
sandbox_v1_2 = modal.Sandbox.create("sleep", "20", app=sb_app)
sandbox_v1_1.set_tags({"major_version": "1", "minor_version": "1"})
sandbox_v1_2.set_tags({"major_version": "1", "minor_version": "2"})
for sandbox in modal.Sandbox.list(app_id=sb_app.app_id): # All sandboxes.
print(sandbox.object_id)
for sandbox in modal.Sandbox.list(
app_id=sb_app.app_id,
tags={"major_version": "1"},
): # Also all sandboxes.
print(sandbox.object_id)
for sandbox in modal.Sandbox.list(
app_id=sb_app.app_id,
tags={"major_version": "1", "minor_version": "2"},
): # Just the latest sandbox.
print(sandbox.object_id)
sandbox_v1_1.detach()
sandbox_v1_2.detach()sandbox_v1_1 = await modal.Sandbox.create.aio("sleep", "10", app=sb_app)
sandbox_v1_2 = await modal.Sandbox.create.aio("sleep", "20", app=sb_app)
await sandbox_v1_1.set_tags.aio({"major_version": "1", "minor_version": "1"})
await sandbox_v1_2.set_tags.aio({"major_version": "1", "minor_version": "2"})
async for sandbox in modal.Sandbox.list.aio(app_id=sb_app.app_id): # All sandboxes.
print(sandbox.object_id)
async for sandbox in modal.Sandbox.list.aio(
app_id=sb_app.app_id,
tags={"major_version": "1"},
): # Also all sandboxes.
print(sandbox.object_id)
async for sandbox in modal.Sandbox.list.aio(
app_id=sb_app.app_id,
tags={"major_version": "1", "minor_version": "2"},
): # Just the latest sandbox.
print(sandbox.object_id)
await sandbox_v1_1.detach.aio()
await sandbox_v1_2.detach.aio()const sandboxV1_1 = await modal.sandboxes.create(app, image, {
command: ["sleep", "10"],
});
const sandboxV1_2 = await modal.sandboxes.create(app, image, {
command: ["sleep", "20"],
});
await sandboxV1_1.setTags({ major_version: "1", minor_version: "1" });
await sandboxV1_2.setTags({ major_version: "1", minor_version: "2" });
// All sandboxes.
for await (const sandbox of modal.sandboxes.list({ appId: app.appId })) {
console.log(sandbox.sandboxId);
}
// Also all sandboxes.
for await (const sandbox of modal.sandboxes.list({
appId: app.appId,
tags: { major_version: "1" },
})) {
console.log(sandbox.sandboxId);
}
// Just the latest sandbox.
for await (const sandbox of modal.sandboxes.list({
appId: app.appId,
tags: { major_version: "1", minor_version: "2" },
})) {
console.log(sandbox.sandboxId);
}
sandboxV1_1.detach();
sandboxV1_2.detach();sandboxV1_1, err := mc.Sandboxes.Create(ctx, app, image, &modal.SandboxCreateParams{
Command: []string{"sleep", "10"},
})
sandboxV1_2, err := mc.Sandboxes.Create(ctx, app, image, &modal.SandboxCreateParams{
Command: []string{"sleep", "20"},
})
defer sandboxV1_1.Detach()
defer sandboxV1_2.Detach()
sandboxV1_1.SetTags(ctx, map[string]string{"major_version": "1", "minor_version": "1"})
sandboxV1_2.SetTags(ctx, map[string]string{"major_version": "1", "minor_version": "2"})
// All sandboxes.
it, _ := mc.Sandboxes.List(ctx, &modal.SandboxListParams{
AppID: app.AppID,
})
for sandbox := range it {
fmt.Println(sandbox.SandboxID)
}
// Also all sandboxes.
it, _ = mc.Sandboxes.List(ctx, &modal.SandboxListParams{
AppID: app.AppID,
Tags: map[string]string{"major_version": "1"},
})
for sandbox := range it {
fmt.Println(sandbox.SandboxID)
}
// Just the latest sandbox.
it, _ = mc.Sandboxes.List(ctx, &modal.SandboxListParams{
AppID: app.AppID,
Tags: map[string]string{"major_version": "1", "minor_version": "2"},
})
for sandbox := range it {
fmt.Println(sandbox.SandboxID)
}Cleaning up Client-side Connections
Unlike other Modal objects, the local Sandbox will hold a direct connection to
its compute substrate. While this connection should be automatically closed
during garbage collection, we recommend explicitly cleaning up the resources
once you are finished interacting with the Sandbox by calling its detach() method:
sb = modal.Sandbox.create(app=sb_app)
sb.detach()sb = await modal.Sandbox.create.aio(app=sb_app)
await sb.detach.aio()const sb = await modal.sandboxes.create(app, image);
sb.detach();sb, err := mc.Sandboxes.Create(ctx, app, image, nil)
defer sb.Detach()After calling detach, any operation using the Sandbox object is not guaranteed to
work. If you want to continue interacting with a running sandbox, use Sandbox.from_id to get a new Sandbox object that references the original sandbox. In the Python SDK, terminate leaves your sandbox attached, so we recommend calling detach after you
are done with your terminated sandbox. In the Go/JS SDK, Terminate will also detach
your sandbox.