Sandboxes (original) (raw)

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:

Each individual job is called a Sandbox and can be created using the Sandbox.create constructor:

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

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.

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:

  1. It has an active command running (via sb.exec(...))
  2. Its stdin is being written to (via sb.stdin.write())
  3. 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:

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:

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:

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:

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:

Configuration

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:

Environments

Environment variables

You can set environment variables using inline secrets:

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:

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.

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:

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.

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.

Logging

You can see Sandbox execution logs using the verbose option. For example:

shows Sandbox logs:

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.

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.

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.

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:

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.