> ## Documentation Index
> Fetch the complete documentation index at: https://docs.porter.run/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript Sandbox SDK volumes

> Create, mount, browse, and read sandbox volumes with the TypeScript Sandbox SDK

Volumes provide persistent storage that can be mounted into Porter Sandboxes. Create a volume before launching the sandbox, then pass the volume ID in `volume_mounts`.

<Warning>
  Sandboxes are in a private beta. Please reach out to us at [support@porter.run](mailto:support@porter.run) or over Slack if you are interested in joining.
</Warning>

## Create and mount a volume

```typescript theme={null}
import { Porter } from "porter-sandbox";

const porter = new Porter();

const volume = await porter.volumes.create({
  name: "agent-workspace",
});

const sandbox = await porter.sandboxes.create({
  image: "python:3.12-slim",
  volume_mounts: {
    "/workspace": volume.id,
  },
});

try {
  await sandbox.exec(["python", "-c", "open('/workspace/result.txt', 'w').write('done')"]);
  const result = await sandbox.exec(["cat", "/workspace/result.txt"]);
  console.log(result.stdout);
} finally {
  await sandbox.terminate();
  porter.close();
}
```

`volume_mounts` is an object keyed by the absolute mount path inside the sandbox. Each value is a volume ID.

## List volumes

`list()` returns volume handles:

```typescript theme={null}
import { Porter } from "porter-sandbox";

const porter = new Porter();

for (const volume of await porter.volumes.list()) {
  console.log(volume.id, volume.name, volume.phase, volume.attachedTo, volume.path);
}

porter.close();
```

## Get a volume by name

Volume names are unique within the cluster. Use `get(name)` when you know a volume name:

```typescript theme={null}
import { Porter } from "porter-sandbox";

const porter = new Porter();
const volume = await porter.volumes.get("agent-workspace");

console.log(volume.id);

porter.close();
```

Volume names may contain lowercase letters, numbers, and hyphens, and must start and end with a letter or number. A volume name must be unique within the cluster for the lifetime of the volume. After the volume is deleted, the name can be used again.

## Inspect a volume

```typescript theme={null}
import { Porter } from "porter-sandbox";

const porter = new Porter();
const volume = await porter.volumes.get("agent-workspace");

console.log(volume.name, volume.phase, volume.attachedTo, volume.createdAt);
porter.close();
```

## Browse volume contents

`listdir` returns the entries directly inside a directory, directories first:

```typescript theme={null}
import { Porter } from "porter-sandbox";

const porter = new Porter();
const volume = await porter.volumes.get("agent-workspace");

for (const file of await volume.listdir("/checkpoints")) {
  console.log(file.path, file.isDirectory ? "dir" : file.sizeBytes);
}

porter.close();
```

`iterdir` walks the whole tree instead, descending into every subdirectory:

```typescript theme={null}
for await (const file of volume.iterdir("/checkpoints")) {
  console.log(file.path, file.sizeBytes);
}
```

`search` walks the tree and returns only the entries whose name contains a substring, optionally rooted at a subdirectory:

```typescript theme={null}
const configs = await volume.search("config.json");
const checkpointConfigs = await volume.search("config.json", { path: "/checkpoints" });
```

## Read a file

`readText` returns a whole file as a string, and `readFile` returns raw bytes:

```typescript theme={null}
const config = await volume.readText("/checkpoints/config.json");
const bytes = await volume.readFile("/checkpoints/weights.bin");
```

Both accept `offset` and `length` to read part of a file:

```typescript theme={null}
const head = await volume.readText("/logs/train.log", { offset: 0, length: 512 });
```

For files too large to hold in memory, `stream` pages through the file in chunks (8 MiB by default):

```typescript theme={null}
import { createWriteStream } from "node:fs";

const out = createWriteStream("model.safetensors");
for await (const chunk of volume.stream("/checkpoints/model.safetensors")) {
  out.write(chunk);
}
out.end();
```

Reading a path that does not exist throws `NotFoundError`.

## Access volume data from apps

Volumes live on a shared disk that apps on the same cluster can attach. Attach the disk named `sandbox-volumes` to a service in your app; it mounts at `/data/<app-name>/sandbox-volumes` and contains one subdirectory per volume.

Each volume handle exposes a `path` with the volume's subdirectory on that disk, so an app reads a volume's data at `/data/<app-name>/sandbox-volumes/<path>`:

```typescript theme={null}
import { Porter } from "porter-sandbox";

const porter = new Porter();
const volume = await porter.volumes.get("agent-workspace");

console.log(volume.path); // sandbox-vol-2f1c8b7e-...
porter.close();
```

The disk is a live view: writes a sandbox makes to its mounted volume are visible to apps right away, and new volumes show up as new subdirectories without redeploying the app.

<Warning>
  Volume contents are written by sandboxed workloads, which are often running untrusted code. Treat anything your app reads from this disk as untrusted input: hostile file contents, names, or sizes can exploit vulnerabilities in the code that processes them. Porter isolates the sandboxes themselves but does not inspect or sanitize what they write, so validating this data before acting on it is your application's responsibility.
</Warning>

## Delete a volume

Delete volumes by name:

```typescript theme={null}
import { Porter } from "porter-sandbox";

const porter = new Porter();

await porter.volumes.delete("agent-workspace");
porter.close();
```

Deleting a volume fails while it is attached to a sandbox. Terminate any attached sandboxes before deleting the volume.

## Next steps

* [TypeScript Sandbox SDK quickstart](/sandboxes/sdk/typescript/quickstart)
* [TypeScript Sandbox SDK reference](/sandboxes/sdk/typescript/reference)
* [TypeScript Sandbox SDK errors](/sandboxes/sdk/typescript/errors)
