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

# Upload & download files

> Move files in and out of a BoxLite sandbox — upload a file, run a script on it, and download the results using three different methods.

Most real workloads need data. This tutorial shows you how to get files into a sandbox, process them, and get results back out. You'll learn three methods and when to use each one.

## Method comparison

| Method                     | Direction     | Best for               | Size limit           |
| -------------------------- | ------------- | ---------------------- | -------------------- |
| `copy_in()` / `copy_out()` | Host ↔ Guest  | Files and directories  | Large files          |
| Volume mounts              | Both (shared) | Datasets, config files | No limit             |
| `exec` + base64            | Either        | Small inline data      | \~1 MB (shell limit) |

## Prerequisites

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    pip install boxlite
    ```
  </Tab>

  <Tab title="Node.js">
    ```bash theme={null}
    npm install @boxlite-ai/boxlite
    ```
  </Tab>
</Tabs>

## Method 1: copy\_in / copy\_out

The most common approach. Upload a file, process it, download the results.

<Tabs>
  <Tab title="Python">
    ```python title="file_transfer.py" theme={null}
    import asyncio
    import boxlite


    async def main():
        async with boxlite.SimpleBox(image="python:slim") as box:
            # Upload a script into the box
            # Note: destination must be a directory path, not a file path
            await box.exec("mkdir", "-p", "/workspace")
            await box.copy_in("/host/script.py", "/workspace/")

            # Run the script
            result = await box.exec("python", "/workspace/script.py")
            print(f"Exit code: {result.exit_code}")
            print(result.stdout)

            # Download the results
            await box.copy_out("/workspace/output.json", "/host/")


    if __name__ == "__main__":
        asyncio.run(main())
    ```
  </Tab>

  <Tab title="Node.js">
    <Note>
      The Node.js SDK does not currently expose `copyIn()` / `copyOut()` methods. Use `exec` with shell commands to write files, and `exec` with `cat` to read them back.
    </Note>

    ```javascript title="file_transfer.js" theme={null}
    import { SimpleBox } from '@boxlite-ai/boxlite';

    async function main() {
      const box = new SimpleBox({ image: 'python:slim' });
      try {
        // Create workspace and write a script using a heredoc
        await box.exec('sh', '-c', `mkdir -p /workspace && cat > /workspace/script.py << 'EOF'
    import json
    data = {"result": 42}
    print(json.dumps(data))
    EOF`);

        // Run the script
        const result = await box.exec('python', '/workspace/script.py');
        console.log(`Exit code: ${result.exitCode}`);
        console.log(result.stdout);

        // Read a file back from the box via exec
        const output = await box.exec('cat', '/workspace/script.py');
        console.log(output.stdout);
      } finally {
        await box.stop();
      }
    }

    main();
    ```
  </Tab>
</Tabs>

**When to use:** Dynamic per-request files — scripts, input data, configuration that varies across runs.

## Method 2: Volume mounts

Mount a host directory directly into the box. Files are shared in real time — no explicit copy step needed.

<Tabs>
  <Tab title="Python">
    ```python title="volume_mount.py" theme={null}
    import asyncio
    import boxlite


    async def main():
        async with boxlite.SimpleBox(
            image="python:slim",
            volumes=[
                ("/host/datasets", "/mnt/data", True),      # Read-only input
                ("/host/results", "/mnt/results", False),    # Writable output
            ],
        ) as box:
            # Input data is already available at /mnt/data
            result = await box.exec(
                "python", "-c",
                "import os; print(os.listdir('/mnt/data'))"
            )
            print(result.stdout)

            # Write results — they appear on the host immediately
            await box.exec(
                "python", "-c",
                "open('/mnt/results/output.txt', 'w').write('done')"
            )


    if __name__ == "__main__":
        asyncio.run(main())
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript title="volume_mount.js" theme={null}
    import { SimpleBox } from '@boxlite-ai/boxlite';

    async function main() {
      const box = new SimpleBox({
        image: 'python:slim',
        volumes: [
          { hostPath: '/host/datasets', guestPath: '/mnt/data', readOnly: true },
          { hostPath: '/host/results', guestPath: '/mnt/results' },
        ],
      });

      try {
        const result = await box.exec(
          'python', '-c',
          "import os; print(os.listdir('/mnt/data'))"
        );
        console.log(result.stdout);
      } finally {
        await box.stop();
      }
    }

    main();
    ```
  </Tab>
</Tabs>

**When to use:** Shared datasets or configuration that multiple boxes need access to. Use read-only mode for input data to prevent accidental modification.

<Warning>
  Volume mounts give the guest direct access to host files. Always use read-only mode (`True`) for input data. Only use `False` for designated output directories.
</Warning>

## Method 3: Inline data via base64

For small payloads (under \~1 MB), you can send data through a shell command without touching the filesystem.

```python title="inline_base64.py" theme={null}
import asyncio
import base64
import boxlite


async def main():
    async with boxlite.SimpleBox(image="python:slim") as box:
        # Encode a small script as base64
        data = b"print('hello from transferred script')"
        encoded = base64.b64encode(data).decode()

        # Send and execute in one command
        result = await box.exec(
            "sh", "-c",
            f"echo {encoded} | base64 -d > /tmp/script.py && python /tmp/script.py",
        )
        print(result.stdout)


if __name__ == "__main__":
    asyncio.run(main())
```

**When to use:** Trivially small payloads where you want to avoid the overhead of `copy_in`. Not suitable for binary files or anything over \~1 MB.

## Putting it together

Here's a realistic workflow: upload a CSV, run analysis code on it, and download the results.

```python title="full_workflow.py" theme={null}
import asyncio
import boxlite


async def main():
    async with boxlite.SimpleBox(
        image="python:slim",
        memory_mib=1024,
    ) as box:
        # 1. Create workspace directory
        await box.exec("mkdir", "-p", "/workspace")

        # 2. Upload the input data
        await box.copy_in("/host/sales_data.csv", "/workspace/")

        # 3. Upload the analysis script
        await box.copy_in("/host/analyze.py", "/workspace/")

        # 4. Run the analysis
        result = await box.exec("python", "/workspace/analyze.py")
        print(f"Analysis completed (exit code: {result.exit_code})")
        print(result.stdout)

        # 5. Download the results
        await box.copy_out("/workspace/report.json", "/host/")
        print("Results saved to /host/report.json")


if __name__ == "__main__":
    asyncio.run(main())
```

<Tip>
  Use `copy_in`/`copy_out` for dynamic per-request files. Use volume mounts for shared datasets. Use inline base64 only for trivially small payloads.
</Tip>

## What's next?

<CardGroup cols={2}>
  <Card title="Connect to an LLM" icon="robot" href="/tutorials/llm-integration">
    Let an LLM generate code and run it in your sandbox automatically.
  </Card>

  <Card title="AI agent integration" icon="gear" href="/guides/ai-agent-integration">
    Production patterns: security presets, concurrency, timeout handling, and resource limits.
  </Card>
</CardGroup>
