---
title: Commands and files
description: Run commands in a sandbox with streamed output, as the default user or as root, and copy files in and out.
---

## Running commands

```sh
pols exec web -- uname -a
pols exec web -- bash -lc 'cd app && npm ci && npm test'
pols exec web --root -- apt-get install -y postgresql-client
pols exec web --cwd /srv --env DEBUG=1 --timeout 1800 -- make
echo 'print(6 * 7)' | pols exec web --stdin -- python3
```

Everything after `--` is the command and its arguments, passed as they are, without a shell. Wrap it in `bash -lc '...'` when you need pipes, redirections, `cd`, `&&` or the login environment.

| Option | Meaning |
| --- | --- |
| `--root` | run as root instead of `user` |
| `--cwd DIR` | working directory; default the user's home |
| `--env KEY=VALUE` | an extra environment variable for this command (repeatable) |
| `--timeout SECONDS` | stop the command after this long; default 600, at most 86,400 |
| `--stdin` | send this machine's standard input to the command (up to 768 KiB) |

The output streams to your terminal as the command produces it, and `pols` exits with the command's exit code. With `--json`, it waits instead and prints one document with `stdout`, `stderr` and `exit_code`.

The sandbox must be running. The command sees the sandbox's own environment variables (from `--env` and `--secret` at creation) plus the ones you pass.

### Over the API

`POST /v1/sandboxes/{sandbox}/exec` takes the command as an argument array:

```json
{ "command": ["bash", "-lc", "npm test"], "cwd": "/home/user/app", "timeout_seconds": 900 }
```

Without special headers, the call waits for the command and returns its exit code and output, each stream capped at 4 MiB (`truncated` says when output was cut). Send `Accept: application/x-ndjson` to stream instead: the response is one JSON event per line, `stdout` and `stderr` chunks as they arrive, ending with an `exit` event with the exit code, or an `error` event. A non-zero exit code is a normal result, not an API error.

## Copying files

```sh
pols cp ./app.tar.gz web:/home/user/app.tar.gz    # upload
pols cp web:/var/log/syslog ./syslog              # download
pols cp web:/etc/os-release -                     # to standard output
tar czf - src | pols cp - web:/home/user/src.tgz  # from standard input
pols cp --root --mode 0755 ./setup.sh web:/usr/local/bin/setup
```

`pols cp` copies one file per call; for a directory, pack it with `tar` and copy the archive. Paths in the sandbox are absolute. Uploads are owned by `user` unless you pass `--root`, and get mode `0644` unless you pass `--mode`. The parent directory must exist. One upload can be up to 1 GiB.

Over the API, `GET /v1/sandboxes/{sandbox}/files?path=...` returns the file's bytes (its permission bits are in the `X-Pols-File-Mode` header) and `PUT` with the same query writes them.
