Running commands in a sandbox
Everything you do inside a sandbox goes through the workspace API: one-off commands, interactive shells, background processes and file transfers. Because nothing connects to the sandbox's own network, it works in every network mode, airgapped included, without a VPN or an SSH key. The zbox CLI, the web console and zbox mcp all use the same calls.
What a command sees
Every command, shell and process in a sandbox runs as root. It inherits the sandbox's environment: the image's environment variables, or the launch environment you replaced them with when you created the sandbox, plus any secrets delivered to the sandbox. Unless the environment sets them, HOME is /root, TERM is xterm-256color and PATH is /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin.
Commands, background processes and shells start in /, whatever the image's WORKDIR is. Pass -w to zbox exec and zbox process to run somewhere else, for example -w /workspace, the working directory of the default base. An SSH session starts in the image's working directory, or in the one you set with zbox new -w.
Commands, shells, background processes and file operations wake a parked sandbox first and then run, so you don't need a separate zbox wake. They also count as activity for the idle TTL. Reading sessions and the process list doesn't wake a sandbox. A sandbox with stale secrets is not woken this way, and the call fails with secretsStale. For more on parking and waking, see implicit wake.
Run a command
zbox exec runs one command to completion and prints its output:
zbox exec app -- python3 -c 'print(1+1)'
# the command is argv, run without a shell: use sh -c for pipes, redirects and variables
zbox exec app -- sh -c 'ls /workspace | wc -l'
# set the working directory and add environment variables for this one command
zbox exec app -w /workspace/api -e DEBUG=1 -- npm test
# stop the command after 30 seconds
zbox exec app --timeout 30s -- npm run build
Stdout and stderr stay separate. zbox exec exits with the command's own exit code. A command that isn't found exits with 127. When zbox itself fails (for example, the sandbox doesn't exist or the workspace doesn't answer), it exits with 125. When --timeout stops the command, it exits with 5. See the CLI exit codes for the rest.
A command has no stdin: piping into zbox exec passes nothing, and a command that waits for input blocks until its timeout. To give a command data, upload it as a file first.
Timeouts and output limits
| Limit | Value |
|---|---|
| Default timeout | 60 seconds |
Maximum timeout (--timeout) | 10 minutes |
| Stdout and stderr in the answer | 4 MiB each, the rest is cut |
| Output recorded in the session | 64 MiB, the rest is dropped |
When the timeout runs out, the command and everything it started are killed.
zbox exec answers only when the command ends. Through the workspace's public API URL, the Zerops edge ends a request that sends nothing for 60 seconds (request limits), so an exec that runs longer fails with 504 Gateway Time-out, even with a longer --timeout. The command keeps running until its own timeout, and its output is recorded in its session. The limit belongs to the edge only: through the API inside the project, http://<hostname>.zerops:8080, an exec runs up to its --timeout. Elsewhere, start anything that can take a minute or more as a background process, or run it in a shell.
Every exec is recorded as a session, so you can read the complete output again later, up to 64 MiB, with stdout and stderr interleaved:
With --json, zbox exec prints the whole result: stdout, stderr, exitCode, timedOut, stdoutTruncated and stderrTruncated (true when the 4 MiB cap cut that stream), and sessionId. The plain output doesn't say when it was cut, so check these flags when it matters.
Open a shell
zbox shell opens an interactive login shell (bash if the image has it, sh otherwise) in a pseudo-terminal:
The terminal takes its size from your local terminal and follows it when you resize the window. Exiting the shell ends the session. The console's Terminal tab opens the same kind of shell in the browser. On a parked sandbox, the console asks before it wakes the sandbox.
If the connection drops, the shell keeps running in the sandbox for 60 seconds. zbox shell reconnects to it on its own and prints resumed session <id>, and the console's terminal tries once. After 60 seconds without a connection, the shell is killed. Nothing a shell prints is recorded, so shell sessions have no output to read later.
For SSH, scp, rsync and editors that connect over SSH, see SSH. To open a shell from your own code, use the shell WebSocket described in the OpenAPI document.
Run a background process
zbox process starts a command in the background and returns its session at once. It takes the same arguments and -w and -e flags as zbox exec, and the process keeps running after the call returns:
zbox session output prints what the process has written so far, up to 64 MiB, with stdout and stderr interleaved. It doesn't follow the output, so run it again to see more. zbox session interrupt sends SIGINT to the process and everything it started. A process that exits on SIGINT closes with exit code 130. To stop a process that ignores SIGINT, use zbox exec app -- kill ….
Starting a process counts as sandbox activity, but a process running on its own doesn't, and neither does reading its output. A sandbox with an idle TTL shorter than a job can be parked while the job is still running.
Sessions
A session is the record of one exec, background process or shell. Its command in the API lists the environment variables you passed with -e by name only (envNames), never their values. zbox session list <sandbox> lists the sandbox's sessions, newest first. Filter them with --kind pty|exec|ssh and --status open|closed|failed. The console's Sessions tab shows the same list and lets you download output and interrupt sessions. How long the workspace keeps closed sessions and their output is listed under retention.
| Status | Meaning |
|---|---|
OPEN | The command, process or shell is still running. |
CLOSED | It ended. A command or process has its exit code, a shell or SSH session has none. |
FAILED | A command or process was cut off: a timeout, a park, a workspace restart, or the sandbox failed. It has no exit code. |
A snapshot or a fork of a running sandbox doesn't interrupt its background processes or shells. A park does:
When a sandbox is parked, its running commands and background processes end as FAILED, and its shells and SSH sessions are closed. Commands and shells don't survive a park. After a park with memory, a background process keeps running once the sandbox wakes, but you can no longer read its output or interrupt it through the session. Use zbox ps and zbox exec … kill instead. A park without memory ends background processes too.
Transfer files
# upload a file; a path ending in / keeps the local file name
zbox file put app ./main.py /workspace/
# upload from stdin, with a file mode
echo 'hello' | zbox file put app - /workspace/hello.txt --mode 0600
# download a file to stdout or to a local file
zbox file get app /workspace/out.txt -o out.txt
# list a directory
zbox file list app /workspace
# download a directory as a tar stream
zbox file archive app /workspace -o workspace.tar
# delete files and directories (directories with their contents)
zbox file remove app /workspace/tmp --yes
- An upload replaces the target file in one step and gets mode
0644unless you pass--mode. The parent directory must exist, otherwise the upload fails withpathNotFound. Create the directory withzbox exec app -- mkdir -p …first. - Through the workspace's public API URL, an upload can be at most 50 MiB, a limit of the Zerops edge. Through the API inside the project and with the CLI logged in to Zerops there is no such cap, and downloads aren't capped either way. To move bigger files in from elsewhere, fetch them from inside the sandbox (
curl,git) or usescporrsyncover SSH. file getdownloads regular files only. Usefile archivefor directories. The archive is an uncompressed tar with the directory itself as its top-level entry, and symlinks are stored as links, not followed.file removedeletes directories recursively. Without a terminal to confirm on, it requires--yes.
The console's Files tab browses directories and uploads, downloads and deletes files the same way.
List processes
zbox ps lists the processes running in a sandbox, without kernel threads: pid, user, state, memory, CPU seconds, how long each has run and the command line.
zbox ps never wakes a sandbox and doesn't count as activity: a sandbox that isn't running lists no processes. The console shows the same list on the Metrics tab when you select Refresh.
For AI agents
- Use
zbox exec --jsonand decide onexitCode,timedOutand the two truncation flags instead of parsing text. - With the API key on the workspace's public API URL, keep each
execwell under 60 seconds. For builds, test suites and servers, start a background process withzbox process -qand check it withzbox session getandzbox session output. - Wrap pipelines and redirects in
sh -c '…', and pass input as a file withzbox file put, because commands have no stdin. - To give an MCP client a sandbox to run code in, use
zbox mcp: itsrun_codetool runs code in a sandbox, andkill_sandboxremoves a sandbox thatrun_codekept.