Skip to content

Create a sandbox, run commands and move files

You’ll create a sandbox, run commands in it, copy a script in and copy a result out. You need a shard server: see Quickstart: server.

The TypeScript and Python tabs reach the server through shard serve, at the address in SHARD_REMOTE and with the API key in SHARD_API_KEY. See Quickstart: client.

Terminal window
# Create a sandbox from an image. Prints the sandbox's ID.
shard create --name agent --memory 512MiB python:3.12
# Run a command in it. Prints Python 3.12 and its patch version.
shard exec agent python --version
# Write a script on your machine, copy it in and run it. Prints 4.
echo 'print(2 + 2)' > main.py
shard cp main.py agent:/srv/main.py
shard exec agent python /srv/main.py
# Write a file in the sandbox and copy it out. The cat prints done.
shard exec agent sh -c 'echo done > /srv/out.txt'
shard cp agent:/srv/out.txt ./out.txt
cat out.txt
# List your sandboxes, then remove this one.
shard list
shard remove --force agent

A new sandbox runs nothing but shard’s own supervisor, and it stays running until you stop it. exec runs one command and returns its output and exit code.

A string command runs through /bin/sh -c, so it can use pipes and redirects. A list runs as is, with no shell.

shard cp works like docker cp. When the destination is a directory in the sandbox, or ends with /, the source goes inside it under its own name, so shard cp ./project agent:/srv/ creates /srv/project.

A copy out works the same way on your machine. If a local path contains a colon, start it with ./ or /.

The SDKs use the exact path you give them: uploadDir("./project", "/srv/project") gives /srv/project the same contents as ./project. A path in the sandbox must always be absolute, because shard has no default directory for a file call.

The SDKs can write and read a sandbox file directly. The CLI has no such verb, so from a shell you write a local file and copy it in.

Terminal window
# Write the file on your machine, then copy it in.
printf '{"n": 3}\n' > input.json
shard cp input.json agent:/srv/input.json

Without an encoding option, files.read returns bytes: a Uint8Array in TypeScript and bytes in Python. These are all the file calls in the SDKs:

TypeScript Python What it does
write, read write, read, read_text put or get bytes or text
upload, download upload, download copy one file between a local path and the sandbox
uploadDir, downloadDir upload_dir, download_dir copy a directory
stat, list stat, list get the type, size, mode, owner and modification time of a path, or of each entry in a directory
mkdir, remove mkdir, remove create a directory, or delete a path; with recursive, delete a directory that is not empty

write, upload and mkdir take parents to create any missing directories, and mode to set the permission bits.

shard cp, upload and download keep the source’s ordinary permission bits, but not setuid or setgid.

A copy into the sandbox runs as a user inside the sandbox. That user is the sandbox’s user unless you set --user on shard cp or user in the SDK.

The guest kernel checks the permissions and sets the owner, just as it does for an exec. A write into the image’s /etc therefore fails for a user other than root. --user root can bypass ordinary file permissions, but not a read-only mount.

  • The sandbox must be running. A copy into or out of a stopped sandbox fails with sandbox <name> is stopped: start it again with shard start <name>. A paused one fails with sandbox <name> is paused: resume it with shard resume <name>.
  • A write never leaves half a file. A copy in writes under a temporary name, syncs, then renames over the old file. If a copy is cut off midway, the old file stays intact.
  • The daemon sets no size cap. Copies stream in both directions, and the daemon never holds the whole file. A directory copied out is the exception: shard cp and the SDKs refuse one past 64 GiB of file data or 1,048,576 entries.
  • A file is only bytes. shard never puts a secret value into a file, even when the file holds a placeholder.
  • A directory copied out is untrusted. The sandbox packs it, so shard cp and the SDKs refuse an absolute name, a .., a hardlink to a name outside it and a device node. No symlink can lead outside the destination. They also drop setuid and setgid bits, and never change an owner on your machine. A refusal names the entry, and the copy writes nothing more.
  • A file call needs the exec scope. A write can replace what the sandbox runs, so a token that copies files can also run commands.

The image needs neither tar nor a shell for a copy, because shard’s own supervisor in the sandbox reads and writes the files. The REST API reference lists every file route.

To learn how a sandbox moves between states, see Sandboxes and the lifecycle.