# Sandboxes | Modal Docs

- **URL:** https://modal.com/docs/guide/sandbox
- **Summary:** This page is a high-level guide to Sandboxes, secure containers for executing untrusted user or agent code on Modal.

Copy page

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](https://modal.com/docs/sdk/py/latest/modal.Sandbox)
.

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`](https://modal.com/docs/sdk/py/latest/modal.Sandbox#create)
 constructor:

PythonPython (async)JavaScriptGo

    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](https://modal.com/docs/guide/apps#entrypoints-for-ephemeral-apps)
.

Sandboxes require an [`App`](https://modal.com/docs/guide/apps)
 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`](https://modal.com/docs/sdk/py/latest/modal.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:

1.  **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`.
    
2.  **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.
    
3.  **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.
    
4.  **Ready** — If [readiness probes](https://modal.com/docs/guide/sandboxes#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.
    
5.  **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 from `sandbox.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.

PythonPython (async)JavaScriptGo

    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](https://modal.com/docs/guide/sandbox-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:

1.  It has an active [command](https://modal.com/docs/guide/sandbox-spawn)
     running (via [`sb.exec(...)`](https://modal.com/docs/sdk/py/latest/modal.Sandbox#exec)
    )
2.  Its stdin is being written to (via [`sb.stdin.write()`](https://modal.com/docs/sdk/py/latest/modal.Sandbox#stdin)
    )
3.  It has an open TCP connection over one of its [Tunnels](https://modal.com/docs/guide/tunnels)
    

### Readiness Probes 

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:

PythonPython (async)JavaScriptGo

    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, {
      command: ["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{
    		Command:        []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:

PythonPython (async)JavaScriptGo

    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, {
      command: ["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{
    		Command:        []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:

PythonPython (async)JavaScriptGo

    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](https://tldp.org/LDP/abs/html/exitcodes.html)
 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`](https://modal.com/docs/sdk/py/latest/modal.container_process#returncode)
     / [`ContainerProcess.poll()`](https://modal.com/docs/sdk/py/latest/modal.container_process#poll)
    )
*   The Sandbox itself (via [`Sandbox.returncode`](https://modal.com/docs/sdk/py/latest/modal.Sandbox#returncode)
     / [`Sandbox.poll()`](https://modal.com/docs/sdk/py/latest/modal.Sandbox#poll)
    )

PythonPython (async)JavaScriptGo

    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) # 137

    sb = 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) # 137

    const 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); // 137

    sb, _ := 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) // 137

Configuration 

Sandboxes support nearly all configuration options found in regular `modal.Function`s. Refer to [`Sandbox.create`](https://modal.com/docs/sdk/py/latest/modal.Sandbox#create)
 for further documentation on Sandbox configs.

For example, Images and Volumes can be used just as with functions:

PythonPython (async)JavaScriptGo

    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:

PythonPython (async)JavaScriptGo

    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](https://modal.com/docs/guide/images)
 just as Functions do. These can be defined using [method chaining](https://modal.com/docs/guide/images)
 or by referencing an [existing Image in an external container registry](https://modal.com/docs/guide/existing-images)
.

#### Separating Image builds from Sandbox creation 

To avoid blocking creation of new Sandboxes on rebuilding an invalidated Image, it’s recommended to use Modal’s named Images with sandboxes, rather than using inline Image definitions.

Use [`Image.build`](https://modal.com/docs/sdk/py/latest/modal.Image#build)
 to trigger Image builds as part of a deployment flow or at a regular interval (e.g., in a [scheduled job](https://modal.com/docs/guide/cron)
 or CI pipeline), then publish the result as a [named image](https://modal.com/docs/guide/named-images)

Sandboxes can then use [`Image.from_name`](https://modal.com/docs/sdk/py/latest/modal.Image#from_name)
 to reference the Image in a way that’s guaranteed to not block on rebuilds.

PythonPython (async)JavaScriptGo

    # build_sandbox_image.py
    app = modal.App.lookup("sandbox-app", create_if_missing=True)
    
    # Method-chained image
    image = modal.Image.debian_slim().pip_install("pandas")
    
    # Or, for an external registry image with a fixed tag:
    # image = modal.Image.from_registry("ubuntu:24.04")
    
    with modal.enable_output():
        image.build(app=app).publish("sandbox-runtime")
    
    # sandbox_app.py
    app = modal.App.lookup("sandbox-app", create_if_missing=True)
    
    image = modal.Image.from_name("sandbox-runtime")
    modal.Sandbox.create(app=app, image=image)

    # build_sandbox_image.py
    app = await modal.App.lookup.aio("sandbox-app", create_if_missing=True)
    
    # Method-chained image
    image = modal.Image.debian_slim().pip_install("pandas")
    
    # Or, for an external registry image with a fixed tag:
    # image = modal.Image.from_registry("ubuntu:24.04")
    
    with modal.enable_output():
        await image.build.aio(app)
    await image.publish.aio("sandbox-runtime")
    
    # app.py
    app = await modal.App.lookup.aio("sandbox-app", create_if_missing=True)
    
    image = modal.Image.from_name("sandbox-runtime")
    await modal.Sandbox.create.aio(app=app, image=image)

    // build_sandbox_image.ts
    const app = await modal.apps.fromName("sandbox-app", {
      createIfMissing: true,
    });
    
    const image = modal.images
      .fromRegistry("python:3.13-slim")
      .dockerfileCommands(["RUN pip install pandas"]);
    
    await image.build(app);
    await image.publish("sandbox-runtime");
    
    // app.ts
    const app = await modal.apps.fromName("sandbox-app", {
      createIfMissing: true,
    });
    
    const image = await modal.images.fromName("sandbox-runtime");
    await modal.sandboxes.create(app, image);

    // build_sandbox_image.go
    app, err := mc.Apps.FromName(ctx, "sandbox-app", &modal.AppFromNameParams{
    	CreateIfMissing: true,
    })
    
    image := mc.Images.FromRegistry("python:3.13-slim", nil).
    	DockerfileCommands([]string{"RUN pip install pandas"}, nil)
    
    builtImage, err := image.Build(ctx, app, nil)
    err = builtImage.Publish(ctx, "sandbox-runtime", nil)
    
    // app.go
    app, err = mc.Apps.FromName(ctx, "sandbox-app", &modal.AppFromNameParams{
    	CreateIfMissing: true,
    })
    
    image, err = mc.Images.FromName(ctx, "sandbox-runtime", nil)
    sb, err := mc.Sandboxes.Create(ctx, app, image, nil)
    defer sb.Detach()

#### Image build logs 

You may need to manually enable output streaming to see your image build logs:

PythonPython (async)JavaScriptGo

    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:

PythonPython (async)JavaScriptGo

    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, {
      command: ["python", "-m", "http.server", "8080"],
      timeoutMs: 10 * 1000,
    });
    sb.detach();

    sb, err := mc.Sandboxes.Create(ctx, app, image, &modal.SandboxCreateParams{
      Command: []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](https://modal.com/docs/examples/jupyter_sandbox)
 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.

PythonPython (async)JavaScriptGo

    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_id`s 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.

PythonPython (async)JavaScriptGo

    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.

PythonPython (async)JavaScriptGo

    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`.

PythonPython (async)JavaScriptGo

    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:

PythonPython (async)JavaScriptGo

    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.

[Sandboxes](https://modal.com/docs/guide/sandbox#sandboxes)
[What are Sandboxes and why should I use them?](https://modal.com/docs/guide/sandbox#what-are-sandboxes-and-why-should-i-use-them)
[Lifecycle](https://modal.com/docs/guide/sandbox#lifecycle)
[Events](https://modal.com/docs/guide/sandbox#events)
[Timeouts](https://modal.com/docs/guide/sandbox#timeouts)
[Idle Timeouts](https://modal.com/docs/guide/sandbox#idle-timeouts)
[Readiness Probes](https://modal.com/docs/guide/sandbox#readiness-probes)
[TCP readiness probe](https://modal.com/docs/guide/sandbox#tcp-readiness-probe)
[Exec readiness probe](https://modal.com/docs/guide/sandbox#exec-readiness-probe)
[Return Codes](https://modal.com/docs/guide/sandbox#return-codes)
[Configuration](https://modal.com/docs/guide/sandbox#configuration)
[Environments](https://modal.com/docs/guide/sandbox#environments)
[Environment variables](https://modal.com/docs/guide/sandbox#environment-variables)
[Custom Images](https://modal.com/docs/guide/sandbox#custom-images)
[Separating Image builds from Sandbox creation](https://modal.com/docs/guide/sandbox#separating-image-builds-from-sandbox-creation)
[Image build logs](https://modal.com/docs/guide/sandbox#image-build-logs)
[Running a Sandbox with an entrypoint](https://modal.com/docs/guide/sandbox#running-a-sandbox-with-an-entrypoint)
[Referencing Sandboxes from other code](https://modal.com/docs/guide/sandbox#referencing-sandboxes-from-other-code)
[Named Sandboxes](https://modal.com/docs/guide/sandbox#named-sandboxes)
[Tagging](https://modal.com/docs/guide/sandbox#tagging)
[Cleaning up Client-side Connections](https://modal.com/docs/guide/sandbox#cleaning-up-client-side-connections)

See it in action

[Building a coding agent with Sandboxes](https://modal.com/docs/examples/agent)

[Building a code interpreter](https://modal.com/docs/examples/simple_code_interpreter)

[Running a Jupyter notebook](https://modal.com/docs/examples/jupyter_sandbox)

[Serving OpenCode for remote agents](https://modal.com/docs/examples/opencode_server)
