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()
    sb.wait(raise_on_termination=False)
    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()
sb.wait(raise_on_termination=False)
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 additionally provide commands mkdir, rm, and ls to make interacting with the filesystem more ergonomic.