Filesystem Access
There are multiple options for uploading files to a Sandbox and accessing them from outside the Sandbox.
Efficient file syncing
To efficiently upload local files to a Sandbox, you can use the add_local_file
and add_local_dir
methods on the Image
class:
sb = modal.Sandbox.create(
app=my_app,
image=modal.Image.debian_slim().add_local_dir(
local_path="/home/user/my_dir",
remote_path="/app"
)
)
p = sb.exec("ls", "/app")
print(p.stdout.read())
p.wait()
Alternatively, it’s possible to use Modal Volumes or CloudBucketMounts. These have the benefit that files created from inside the Sandbox can easily be accessed outside the Sandbox.
To efficiently upload files to a Sandbox using a Volume, you can use the batch_upload
method on the Volume
class - for instance, using an ephemeral Volume that
will be garbage collected when the App finishes:
with modal.Volume.ephemeral() as vol:
import io
with vol.batch_upload() as batch:
batch.put_file("local-path.txt", "/remote-path.txt")
batch.put_directory("/local/directory/", "/remote/directory")
batch.put_file(io.BytesIO(b"some data"), "/foobar")
sb = modal.Sandbox.create(
volumes={"/cache": vol},
app=my_app,
)
p = sb.exec("cat", "/cache/remote-path.txt")
print(p.stdout.read())
p.wait()
sb.terminate()
The caller also can access files created in the Volume from the Sandbox, even after the Sandbox is terminated:
with modal.Volume.ephemeral() as vol:
sb = modal.Sandbox.create(
volumes={"/cache": vol},
app=my_app,
)
p = sb.exec("bash", "-c", "echo foo > /cache/a.txt")
p.wait()
sb.terminate()
for data in vol.read_file("a.txt"):
print(data)
Alternatively, if you want to persist files between Sandbox invocations (useful
if you’re building a stateful code interpreter, for example), you can use create
a persisted Volume
with a dynamically assigned label:
session_id = "example-session-id-123abc"
vol = modal.Volume.from_name(f"vol-{session_id}", create_if_missing=True)
sb = modal.Sandbox.create(
volumes={"/cache": vol},
app=my_app,
)
p = sb.exec("bash", "-c", "echo foo > /cache/a.txt")
p.wait()
sb.terminate()
for data in vol.read_file("a.txt"):
print(data)
File syncing behavior differs between Volumes and CloudBucketMounts. For Volumes, files are only synced back to the Volume when the Sandbox terminates. For CloudBucketMounts, files are synced automatically.
Filesystem API (Alpha)
If you’re less concerned with efficiency of uploads and want a convenient way to pass data in and out of the Sandbox during execution, you can use our filesystem API to easily read and write files. The API supports reading files up to 100 MiB and writes up to 1 GiB in size.
This API is currently in Alpha, and we don’t recommend using it for production workloads.
import modal
app = modal.App.lookup("sandbox-fs-demo", create_if_missing=True)
sb = modal.Sandbox.create(app=app)
with sb.open("test.txt", "w") as f:
f.write("Hello World\n")
f = sb.open("test.txt", "rb")
print(f.read())
f.close()
The filesystem API is similar to Python’s built-in io.FileIO and supports many of the same methods, including read
, readline
, readlines
, write
, flush
, seek
, and close
.
We also provide the special methods replace_bytes
and delete_bytes
, which may be useful for LLM-generated code.
from modal.file_io import delete_bytes, replace_bytes
with sb.open("example.txt", "w") as f:
f.write("The quick brown fox jumps over the lazy dog")
with sb.open("example.txt", "r+") as f:
# The quick brown fox jumps over the lazy dog
print(f.read())
# The slow brown fox jumps over the lazy dog
replace_bytes(f, b"slow", start=4, end=9)
# The slow red fox jumps over the lazy dog
replace_bytes(f, b"red", start=9, end=14)
# The slow red fox jumps over the dog
delete_bytes(f, start=32, end=37)
f.seek(0)
print(f.read())
sb.terminate()
We additionally provide commands mkdir
, rm
, and ls
to make interacting with the filesystem more ergonomic.