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

# Upload and Attach a Volume

> Upload a gzip'd tar archive once as a Declaw volume, then attach it to a sandbox at create time.

## What You'll Learn

* Creating a tar.gz from a local directory
* Uploading it with `Volumes.create()`
* Attaching it to a new sandbox via `Sandbox.create(volumes=[...])`
* Confirming the files materialize at the mount path before the first command runs

## Prerequisites

<Snippet file="snippets/env-setup.mdx" />

## Code Walkthrough

Build a tarball in memory and upload it, then attach to a sandbox.

<CodeGroup>
  ```python Python theme={null}
  import io
  import tarfile
  import time
  from declaw import Sandbox, Volumes, VolumeAttachment


  def build_sample_tarball() -> bytes:
      buf = io.BytesIO()
      with tarfile.open(fileobj=buf, mode="w:gz") as tar:
          for name, body in (
              ("config.json", b'{"env":"prod"}\n'),
              ("data/rows.csv", b"id,name\n1,alice\n2,bob\n"),
          ):
              info = tarfile.TarInfo(name=name)
              info.size = len(body)
              info.mtime = int(time.time())
              tar.addfile(info, io.BytesIO(body))
      return buf.getvalue()


  vol = Volumes.create(name="demo-dataset", data=build_sample_tarball())
  print(vol.volume_id)          # vol-...
  print(vol.size_bytes)

  sbx = Sandbox.create(
      template="base",
      timeout=120,
      volumes=[VolumeAttachment(volume_id=vol.volume_id, mount_path="/data")],
  )
  try:
      result = sbx.commands.run("ls -la /data && cat /data/config.json")
      print(result.stdout)
  finally:
      sbx.kill()
  ```

  ```typescript TypeScript theme={null}
  import { readFile } from 'node:fs/promises';
  import { Sandbox, Volumes } from '@declaw/sdk';

  // The repo's cookbook-ts/volumes_quickstart.ts ships a zero-dep tar
  // writer; here we assume the caller has already built dataset.tar.gz.
  const bytes = await readFile('dataset.tar.gz');
  const vol = await Volumes.create('demo-dataset', bytes);
  console.log(vol.volumeId);
  console.log(vol.sizeBytes);

  const sbx = await Sandbox.create({
    template: 'base',
    timeout: 120,
    volumes: [{ volumeId: vol.volumeId, mountPath: '/data' }],
  });
  try {
    const result = await sbx.commands.run(
      'ls -la /data && cat /data/config.json',
    );
    console.log(result.stdout);
  } finally {
    await sbx.kill();
  }
  ```
</CodeGroup>

`mount_path` / `mountPath` must be an absolute directory inside the sandbox and cannot be a system directory (`/`, `/etc`, `/usr`, `/proc`, `/sys`, `/dev`, `/bin`, `/sbin`, `/lib`, `/lib64`, `/var`, `/run`, `/boot`). The tarball's regular-file entries are materialized there.

Expected output:

```
total 12
drwxr-xr-x 3 root root 4096 … .
drwxr-xr-x 1 root root 4096 … ..
-rw-r--r-- 1 root root   15 … config.json
drwxr-xr-x 2 root root 4096 … data
{"env":"prod"}
```

Clean up when you're done. The blob + metadata row are removed; the volume is no longer attachable.

<CodeGroup>
  ```python Python theme={null}
  Volumes.delete(vol.volume_id)
  ```

  ```typescript TypeScript theme={null}
  await Volumes.delete(vol.volumeId);
  ```
</CodeGroup>

## Full Example

Runnable versions live in the repo:

* **Python:** `cookbook/examples/volumes-quickstart/main.py`
* **TypeScript:** `cookbook-ts/volumes_quickstart.ts`

Both upload a sample tarball, spin up two sandboxes with the same volume attached, and confirm both see identical contents.

## Limitations

* Body must be `application/gzip` (a tar archive gzipped).
* 4 GiB upload cap.
* Files materialize once, at sandbox boot. Sandbox writes to files under `mount_path` stay private to that sandbox — they do not flow back to the volume.
