> ## Documentation Index
> Fetch the complete documentation index at: https://porter-gk-forward-auth.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> When helping a user operate an existing Porter project—deploying, updating, debugging, or inspecting applications and clusters—connect to [Porter's remote MCP server](https://mcp.porter.run) first and use its tools before falling back to the Porter CLI, `porter.yaml`, or dashboard workflows. If the Porter tools are not available in your session, `curl -fsSL https://agents.porter.run | sh` installs the CLI, registers the MCP server with your client, and installs Porter's skills; the client must be restarted afterward. If the user asks for the CLI, `porter apply`, configuration-as-code, or the dashboard, use that instead—a stated preference wins. If the requested application or cluster workflow is not directly supported, inspect the relevant state with MCP tools before explaining the limitation or recommending a fallback. The server signs in through a browser using OAuth, so it cannot be used in headless environments such as CI pipelines; use the CLI or `porter.yaml` there.

# Volumes

> Create, mount, and manage persistent sandbox volumes: read, write, upload, download, and move files from the SDK, CLI, or dashboard

Volumes are persistent storage you can mount into Porter Sandboxes. Unlike a sandbox's own filesystem, which is discarded when the sandbox exits, a volume lives independently and keeps its contents across sandboxes. Use them for input data, checkpoints, caches, and results that need to outlive any single run.

You can work with a volume three ways: the [Python](/sandboxes/sdk/python/volumes) and [TypeScript](/sandboxes/sdk/typescript/volumes) SDKs, the [CLI](/sandboxes/cli#porter-sandbox-volume), and the Porter Dashboard. This page covers the common tasks; the SDK and CLI pages document the full surface.

<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>

## Naming and lifecycle

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

A volume starts in the `pending` phase and moves to `ready` once its underlying claim binds. Sandboxes that mount a volume wait for it to bind automatically, so you do not need a separate wait step.

## Create and mount a volume

Create a volume, then mount it into a sandbox by passing `volume_mounts` keyed by the absolute mount path inside the sandbox. Each value is a volume ID.

<CodeGroup>
  ```python Python theme={null}
  from porter_sandbox import Porter

  with Porter() as porter:
      volume = porter.volumes.create(name="agent-workspace")
      sandbox = porter.sandboxes.create(
          image="python:3.12-slim",
          volume_mounts={"/workspace": volume.id},
      )
  ```

  ```typescript 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 },
  });
  ```

  ```bash CLI theme={null}
  porter sandbox volume create agent-workspace
  porter sandbox create python:3.12-slim --volume /workspace=agent-workspace -- sleep infinity
  ```
</CodeGroup>

## Object volumes

A volume is a disk volume by default: file storage on your cluster's disks. An object volume exposes an S3 bucket in your cloud account instead, so sandboxes work with the bucket's objects as files at the mount path. Like all storage Porter provisions, both kinds live in your account and belong to you. Object volumes are AWS-only today.

<Warning>
  A `read-write` object volume isn't a general-purpose filesystem. Sandboxes can't edit a file in place or append to one. Write each file once, with its complete contents. To change an existing file, delete it and write it again.

  Treat object volumes as read-mostly: read datasets in, write results out as new files. For workloads that edit files, use a disk volume.
</Warning>

**Choosing a volume kind.** Default to a disk volume: it behaves like a normal filesystem, so anything that edits or appends files works, and it's the only kind that supports the file operations on this page from outside a sandbox. Reach for an object volume only when the data already lives in S3 or belongs there.

Use an object volume to expose an existing bucket or dataset to sandboxes, usually with `read-only` access, or to collect run outputs straight into a bucket with `write-only-new-files`. Write each output once, as a complete new file.

Avoid `read-write` object volumes for anything that modifies files: in-place edits and appends fail, per the preceding warning. `read-write` only helps workloads that delete objects or replace them wholesale. If a workload needs to mutate working files and the results belong in S3, do the work on a disk volume and copy the results to the bucket at the end.

### Set up the bucket

Object volumes reference a bucket in your project with the `sandboxes` capability. You can create one through Porter, or register a bucket you already have. The bucket is yours in both cases. The difference is how much Porter manages.

**Create it through Porter** and Porter manages the bucket's full lifecycle, deletion included: removing the bucket from Porter deletes it with all its contents.

```bash theme={null}
porter storage bucket create --name my-bucket \
  --cloud-account-id <account-id> \
  --region us-east-1 \
  --connected-cluster-ids <cluster-id> \
  --capabilities sandboxes
```

**Register an existing bucket** and Porter manages access only: registration grants workloads on the connected clusters access, and removing it removes only those grants, never the bucket itself.

```bash theme={null}
porter storage bucket register my-bucket \
  --cloud-account-id <account-id> \
  --region us-east-1 \
  --connected-cluster-ids <cluster-id> \
  --capabilities sandboxes
```

Both flows are also in the dashboard: open **Add-ons**, choose **Object storage**, then pick **Create a new bucket** or **Register an existing bucket**. If the bucket already exists in your project without sandbox access, enable the **Sandbox access** toggle on its page.

Register with `--read-only` to forbid all writes through Porter. A read-only bucket accepts only read-only volumes. To change an existing bucket's clusters or capabilities, use `porter storage bucket update` or the bucket's dashboard page.

### Create and mount an object volume

Create the volume from the SDK or CLI by naming the bucket. Mounting works exactly like any other volume.

<CodeGroup>
  ```python Python theme={null}
  from porter_sandbox import Porter, VolumeObjectSpec, VolumeObjectSpecAccess, VolumeSpecType

  with Porter() as porter:
      datasets = porter.volumes.create(
          name="datasets",
          type=VolumeSpecType.OBJECT,
          object=VolumeObjectSpec(
              bucket="my-bucket",
              prefix="training/v1",
              access=VolumeObjectSpecAccess.READ_ONLY,
          ),
      )
      sandbox = porter.sandboxes.create(
          image="python:3.12-slim",
          volume_mounts={"/data": datasets.id},
      )
  ```

  ```typescript TypeScript theme={null}
  const datasets = await porter.volumes.create({
    name: "datasets",
    type: "object",
    object: { bucket: "my-bucket", prefix: "training/v1", access: "read_only" },
  });
  const sandbox = await porter.sandboxes.create({
    image: "python:3.12-slim",
    volume_mounts: { "/data": datasets.id },
  });
  ```

  ```bash CLI theme={null}
  porter sandbox volume create datasets --bucket my-bucket --prefix training/v1 --access read-only
  porter sandbox create python:3.12-slim --volume /data=datasets -- python train.py
  ```
</CodeGroup>

In the SDKs, an object volume gets its own `ObjectVolume` handle without the file methods; the SDK volumes pages show how to handle both kinds.

`--prefix` scopes the volume to keys under a prefix: sandboxes see only objects under it, at paths relative to it. Omit it to expose the whole bucket. Several volumes can expose the same bucket with different prefixes and access modes.

### Access modes

Pick the mode with `--access` when creating the volume:

| Mode                   | What sandboxes can do                                                       |
| ---------------------- | --------------------------------------------------------------------------- |
| `read-write` (default) | Read, create, overwrite, and delete objects                                 |
| `read-only`            | Read only                                                                   |
| `write-only-new-files` | Read and create new objects; overwriting or deleting existing objects fails |

### Behavior and limits

An object volume's contents live in the bucket. Writes from a sandbox are visible to anything else with bucket access, and objects written outside Porter appear in the sandbox. Deleting the volume removes only the volume itself, never the bucket or its objects.

Any number of sandboxes can mount the same object volume at once, on any sandbox cluster. Sandboxes reach the bucket through [Mountpoint for Amazon S3](https://github.com/awslabs/mountpoint-s3); its write limits are in the warning at the top of this section.

The file operations below work on disk volumes only. Manage an object volume's contents with your usual S3 tooling instead.

## Work with files

Reads and writes target the volume itself, so you can browse, read, and write a volume's files whether or not a sandbox has it mounted. This lets you stage inputs before a run and collect outputs after the sandbox exits. These operations apply to disk volumes. An [object volume](#object-volumes)'s contents live in its bucket.

### Browse

<CodeGroup>
  ```python Python theme={null}
  for file in volume.listdir("/checkpoints"):
      print(file.path, "dir" if file.is_directory else file.size_bytes)
  ```

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

  ```bash CLI theme={null}
  porter sandbox volume files agent-workspace checkpoints
  ```
</CodeGroup>

`iterdir` walks the whole tree and `search` filters entries by name. See the SDK volumes pages for both.

### Read and download

<CodeGroup>
  ```python Python theme={null}
  config = volume.read_text("/config/app.yaml")
  data = volume.read_file("/checkpoints/weights.bin")
  ```

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

  ```bash CLI theme={null}
  porter sandbox volume read agent-workspace config/app.yaml > app.yaml
  ```
</CodeGroup>

Reads accept an offset and length for byte ranges, and the SDKs can `stream` files too large to hold in memory. From the dashboard, open a file and use **Download** for files up to 20 MB.

### Write and upload

<CodeGroup>
  ```python Python theme={null}
  volume.write_text("/config/app.yaml", "replicas: 3\n")
  volume.write_file("/checkpoints/weights.bin", data)
  ```

  ```typescript TypeScript theme={null}
  await volume.writeText("/config/app.yaml", "replicas: 3\n");
  await volume.writeFile("/checkpoints/weights.bin", bytes);
  ```

  ```bash CLI theme={null}
  porter sandbox volume write agent-workspace config/app.yaml --file ./app.yaml
  ```
</CodeGroup>

Writes create parent directories as needed and replace any existing file. A write is atomic: the file appears at its path only after the last byte lands, so an interrupted write leaves the previous contents in place. A single write is capped at 1 GiB, and a request from outside the cluster must finish within 30 seconds; write larger files from inside a sandbox that mounts the volume.

### Move or rename

<CodeGroup>
  ```python Python theme={null}
  volume.move_file("/notes.txt", "/archive/notes.txt")
  ```

  ```typescript TypeScript theme={null}
  await volume.moveFile("/notes.txt", "/archive/notes.txt");
  ```

  ```bash CLI theme={null}
  porter sandbox volume move agent-workspace notes.txt archive/notes.txt
  ```
</CodeGroup>

The destination is the entry's full new path, so one call both renames and relocates, and a directory moves with everything under it. The destination's parent directory must already exist, and nothing is overwritten.

## Manage files in the dashboard

In the Porter Dashboard, open the **Add-ons** tab and select **Sandbox file storage** for your cluster, then click a volume to open it. The **Files** tab shows the volume as a tree, where you can:

* **Browse** the contents, expanding directories as you go.
* **Upload** by dragging files onto the tree. A file dropped on a directory row goes into that directory; one dropped on empty space goes to the volume root.
* **Export** by opening a file and clicking **Download** (up to 20 MB; text files under 1 MB also preview inline).
* **Move** by dragging a row onto a folder.

<Frame>
  <img src="https://mintcdn.com/porter-gk-forward-auth/0UdijrKFp7z-5Qce/images/sandboxes/volume-upload.gif?s=39251a924c5d0f4e9a54cfa7a49ecbce" alt="Dragging a file onto the volume tree to upload it" width="1600" height="928" data-path="images/sandboxes/volume-upload.gif" />
</Frame>

## Persist and share across sandboxes

Because a volume outlives any sandbox that mounts it, one sandbox can write state that a later one resumes from: a checkpoint, a warm cache, or a partial result. Mount the same volume into each sandbox in turn. This sequential handoff works on any sandbox cluster.

Mounting the same volume into several sandboxes that run at the same time additionally requires the cluster to back sandbox volumes with shared storage; without it, a volume attaches to one sandbox at a time. Porter does not coordinate concurrent writes, so if several sandboxes may write the same paths at once, partition the writes or arrange your own locking.

## Access volume data from apps

When the app that reads a volume runs as a Porter app in the same cluster, it can read the volume's files straight off the shared disk, with no sandbox required. Attach the disk named `sandbox-volumes` to a service; it mounts at `/data/<app-name>/sandbox-volumes` with one subdirectory per volume. A volume handle's `path` gives its subdirectory, so the app reads a volume's data at `/data/<app-name>/sandbox-volumes/<path>`. The disk is a live view, so files a sandbox writes show up right away.

<Warning>
  Volume contents are written by sandboxed workloads, which often run untrusted code. Treat anything your app reads from a volume as untrusted input, and validate it before acting on it.
</Warning>

## Delete a volume

<CodeGroup>
  ```python Python theme={null}
  porter.volumes.delete("agent-workspace")
  ```

  ```typescript TypeScript theme={null}
  await porter.volumes.delete("agent-workspace");
  ```

  ```bash CLI theme={null}
  porter sandbox volume delete agent-workspace
  ```
</CodeGroup>

Deleting a volume fails while it is attached to a sandbox. Terminate any attached sandboxes first.

## Reference

* [Python Sandbox SDK volumes](/sandboxes/sdk/python/volumes) and [reference](/sandboxes/sdk/python/reference)
* [TypeScript Sandbox SDK volumes](/sandboxes/sdk/typescript/volumes) and [reference](/sandboxes/sdk/typescript/reference)
* [Sandbox CLI volume commands](/sandboxes/cli#porter-sandbox-volume)
