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

# File operations

> Move files between your laptop and your machines, and between machines.

`boxd machine cp` copies files in either direction. It works the same from your laptop and from inside a machine, and it does not need SSH keys or any setup.

## Upload and download

```bash theme={"theme":"github-dark"}
boxd machine cp ./local.txt myapp:/home/boxd/remote.txt    # upload
boxd machine cp myapp:/home/boxd/remote.txt ./local.txt    # download
```

The side with the `NAME:` prefix is the machine. Everything else is your local disk.

## Where paths land

The path after `NAME:` follows three rules:

| You write                    | It resolves to        |
| ---------------------------- | --------------------- |
| `myapp:/etc/app/config.yaml` | used as-is            |
| `myapp:~/notes.md`           | `/home/boxd/notes.md` |
| `myapp:notes.md`             | `/home/boxd/notes.md` |

A bare relative path is the common case, so `boxd machine cp ./notes.md myapp:notes.md` puts the file in the home directory.

## Whole directories

Add `-r`. The directory is tarred on the way out and untarred on arrival, so one call moves the whole tree.

The destination is the **parent** to drop the directory into, not the name it gets:

```bash theme={"theme":"github-dark"}
boxd machine cp -r ./myproject myapp:.        # → /home/boxd/myproject
boxd machine cp -r ./myproject myapp:/opt     # → /opt/myproject
```

Naming the destination after the directory nests it, so `myapp:myproject` gives you `/home/boxd/myproject/myproject`.

<Note>
  Copying from macOS also carries over the `._name` AppleDouble files that `tar` creates for extended attributes. They are harmless, and `find . -name '._*' -delete` inside the machine clears them.
</Note>

## Piping instead of files

Use `-` in place of a path to read from stdin or write to stdout:

```bash theme={"theme":"github-dark"}
echo "hello" | boxd machine cp - myapp:greeting.txt   # upload from stdin
boxd machine cp myapp:/var/log/app.log -              # download to stdout
```

Piping into `machine exec` works too, so `cat data.json | boxd machine exec myapp -- 'jq .'` behaves like a normal shell pipe. For moving bytes into a file, `cp -` stays the cleaner tool. It skips the shell entirely and verifies the byte count on arrival.

To write somewhere root owns, upload to `/tmp` first and move it with `exec`:

```bash theme={"theme":"github-dark"}
cat nginx.conf | boxd machine cp - myapp:/tmp/nginx.conf
boxd machine exec myapp -- 'sudo mv /tmp/nginx.conf /etc/nginx/nginx.conf'
```

## Between two machines

Every machine has the boxd CLI inside, so a machine can copy straight to another machine you own:

```bash theme={"theme":"github-dark"}
boxd machine exec myapp -- 'boxd machine cp ./file myapp-test:/tmp/file'
```

From your laptop there is no direct machine-to-machine copy. Download to your disk first, then upload to the other machine.

## Limits

Uploads and downloads have different ceilings:

* **Uploads stream.** Copying onto a machine has no practical size limit.
* **Downloads to your laptop** move at most 4 MB per `cp`. A `-r` directory counts as one download of its tar.
* **From inside a machine**, `cp` downloads at most 1 GB per file.

For anything above the download limit, stream it through `exec` instead, which has no cap:

```bash theme={"theme":"github-dark"}
boxd machine exec myapp -- 'cat /var/log/big.log' > big.log
boxd machine exec myapp -- 'tar -czf - myproject' > myproject.tgz
```

And for large public assets, download them inside the machine with `curl` or `git`. That uses the machine's own bandwidth rather than yours.
