docs/sandboxes/filesystem.mdx
The filesystem API lets you read, write, and manage files inside a sandbox without mounting volumes or SSH. It uses the same channel as command execution, so it doesn't touch the sandbox's network.
Handy for pushing generated code into a sandbox, pulling back results, or inspecting logs without having to pre-mount a shared directory.
await sb.fs().write("/app/config.json", '{"debug": true}');
await sb.fs.write("/app/config.json", b'{"debug": true}')
const content = await sb.fs().readToString("/app/config.json");
content = await sb.fs.read_text("/app/config.json")
const entries = await sb.fs().list("/app");
for (const entry of entries) {
console.log(`${entry.path}: ${entry.kind}`);
}
entries = await sb.fs.list("/app")
for entry in entries:
print(f"{entry.path}: {entry.kind}")
For files too large to fit in memory, use streaming. Data is transferred in chunks of approximately 3 MiB each.
<CodeGroup> ```rust Rust let mut stream = sb.fs().read_stream("/app/data.bin").await?; while let Some(chunk) = stream.recv().await? { process(&chunk); } ```const stream = await sb.fs().readStream("/app/data.bin");
for await (const chunk of stream) {
processChunk(chunk); // chunk is a Uint8Array
}
stream = await sb.fs.read_stream("/app/data.bin")
async for chunk in stream:
process(chunk)
Copy a file from the host machine into the sandbox in a single call.
<CodeGroup> ```rust Rust sb.fs().copy_from_host("./local-file.txt", "/app/remote-file.txt").await?; ```await sb.fs().copyFromHost("./local-file.txt", "/app/remote-file.txt");
await sb.fs.copy_from_host("./local-file.txt", "/app/remote-file.txt")
The default filesystem backends handle most use cases. For advanced scenarios, you can attach hooks to intercept operations on a volume, or implement a full custom backend.
Intercept file reads and writes on a volume without replacing the entire backend. Hooks receive the path and data, and return transformed data. The underlying filesystem handles everything else (permissions, directories, metadata).
<CodeGroup> ```rust Rust use microsandbox::Sandbox;let key = get_encryption_key(); let sb = Sandbox::builder("encrypted") .image("python") .volume("/secrets", |v| v .bind("/data/secrets") .on_read(move |_path, data| decrypt(data, &key)) .on_write(move |_path, data| encrypt(data, &key)) ) .create().await?;
```typescript TypeScript
import { Sandbox } from "microsandbox";
// Hooks are coming soon. The planned shape will sit alongside the regular
// mount methods on the volume builder, e.g.:
//
// .volume("/secrets", (m) => m
// .bind("/data/secrets")
// .onRead((path, data) => decrypt(data, key))
// .onWrite((path, data) => encrypt(data, key)),
// )
//
// For now the closest equivalent is a plain bind:
await using sb = await Sandbox.builder("encrypted")
.image("python")
.volume("/secrets", (m) => m.bind("/data/secrets"))
.create();
from microsandbox import Sandbox, Volume
key = get_encryption_key()
sb = await Sandbox.create(
"encrypted",
image="python",
volumes={
"/secrets": Volume.bind("/data/secrets",
on_read=lambda path, data: decrypt(data, key),
on_write=lambda path, data: encrypt(data, key),
),
},
)
For full control, implement the FsBackend trait (Rust) or class (TypeScript). This gives you access to every POSIX operation: read, write, lookup, getattr, readdir, etc. You can delegate to a built-in backend (like PassthroughFs) for operations you don't need to customize.
struct EncryptedFs { key: [u8; 32], inner: PassthroughFs }
impl FsBackend for EncryptedFs { fn read(&self, ctx: Context, inode: u64, handle: u64, buf: &mut [u8], offset: u64) -> io::Result<usize> { let n = self.inner.read(ctx, inode, handle, buf, offset)?; self.decrypt_in_place(&mut buf[..n]); Ok(n) } fn write(&self, ctx: Context, inode: u64, handle: u64, buf: &[u8], offset: u64) -> io::Result<usize> { let encrypted = self.encrypt(buf); self.inner.write(ctx, inode, handle, &encrypted, offset) } // ... remaining methods delegate to self.inner }
let sb = Sandbox::builder("custom") .volume("/secrets", |v| v.backend(EncryptedFs::new(key, "/data")?)) .create().await?;
```typescript TypeScript
// FsBackend is coming soon. The planned API will let you plug a custom
// backend into a volume:
//
// class EncryptedFs implements FsBackend {
// // ... implement read, write, and other required methods
// }
//
// const sb = await Sandbox.builder("custom")
// .volume("/secrets", (m) => m.backend(new EncryptedFs(key, "/data")))
// .create();
from microsandbox import FsBackend, Sandbox, Volume
# Implement the FsBackend interface
class EncryptedFs(FsBackend):
# ... implement read, write, and other required methods
pass
sb = await Sandbox.create(
"custom",
volumes={
"/secrets": Volume.backend(EncryptedFs(key, "/data")),
},
)