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:
- 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:
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:
- 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:
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:
- Processes in the sandbox (via ContainerProcess.returncode / ContainerProcess.poll())
- The Sandbox itself (via Sandbox.returncode / Sandbox.poll())
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.