Sandboxes

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:

import modal

app = modal.App.lookup("my-app", create_if_missing=True)

sb = modal.Sandbox.create(app=app)

p = sb.exec("python", "-c", "print('hello')")
print(p.stdout.read())

p = sb.exec("bash", "-c", "for i in {1..10}; do date +%T; sleep 0.5; done")
for line in p.stdout:
    # Avoid double newlines by using end="".
    print(line, end="")

sb.terminate()

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.

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 string arguments to the Sandbox constructor:

sb = modal.Sandbox.create("python", "-m", "http.server", "8080", app=my_app, timeout=10)
for line in sb.stdout:
    print(line, end="")

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 Sandbox.from_id method.

sb = modal.Sandbox.create(app=my_app)
sb_id = sb.object_id

# ... later in the program ...

sb2 = modal.Sandbox.from_id(sb_id)

p = sb2.exec("echo", "hello")
print(p.stdout.read())
sb2.terminate()

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.

Parameters

Sandboxes support nearly all configuration options found in regular modal.Functions. Refer to Sandbox.create for further documentation on Sandbox parameterization.

For example, Images and Mounts can be used just as with functions:

sb = modal.Sandbox.create(
    image=modal.Image.debian_slim().pip_install("pandas"),
    mounts=[modal.Mount.from_local_dir("./my_repo", remote_path="/repo")],
    workdir="/repo",
    app=my_app,
)

Using 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 python call. As such, you 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=my_app)

Dynamically defined environments

Note that any valid Image or Mount can be used with a Sandbox, even if those images or mounts have not previously been defined. This also means that Images and Mounts can be built from requirements at runtime. For example, you could use a language model to write some code and define your image, and then spawn a Sandbox with it. Check out devlooper for a concrete example of this.

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=my_app,
)
p = sb.exec("bash", "-c", "echo $MY_SECRET")
print(p.stdout.read())

Tagging

Sandboxes can 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=my_app)
sandbox_v1_2 = modal.Sandbox.create("sleep", "20", app=my_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=my_app.app_id):  # All sandboxes.
    print(sandbox.object_id)

for sandbox in modal.Sandbox.list(
    app_id=my_app.app_id,
    tags={"major_version": "1"},
):  # Also all sandboxes.
    print(sandbox.object_id)

for sandbox in modal.Sandbox.list(
    app_id=app.app_id,
    tags={"major_version": "1", "minor_version": "2"},
):  # Just the latest sandbox.
    print(sandbox.object_id)