Overview
Execute shell commands inside sandboxes using the exec() method. Commands run via /bin/sh -c, supporting pipes, variables, and shell features.
Prerequisites
Before executing commands, ensure you have:
- Installed the Sandbox SDK (see Quickstart)
- Created a sandbox instance
- Basic familiarity with shell commands and async/await in JavaScript/TypeScript
Basic execution
// Simple command
const result = await sandbox.exec("echo 'Hello World'");
console.log(result.stdout); // "Hello World\n"
console.log(result.exitCode); // 0
console.log(result.success); // true
Shell features
Commands support full shell syntax:
// Pipes and redirection
await sandbox.exec("cat file.txt | grep error > errors.log");
// Multiple commands
await sandbox.exec("npm install && npm test");
// Variables and globs
await sandbox.exec("for f in *.txt; do echo $f; done");
Options
Timeout
Set a maximum execution time (max 60 seconds):
const result = await sandbox.exec("npm install", {
timeoutMs: 30_000 // 30 seconds
});
Max per-command timeout is 60 seconds. For long-running servers, use background: true and redirect output to a log file (for example, >/tmp/server.log 2>&1).
Environment variables
Pass environment variables to individual commands using the env option:
// Single environment variable
const result = await sandbox.exec("echo $MY_VAR", {
env: { MY_VAR: "hello world" }
});
// Multiple environment variables
await sandbox.exec("node app.js", {
env: {
NODE_ENV: "production",
PORT: "3000",
API_KEY: "secret-key-12345",
DEBUG: "true"
}
});
// Use in shell commands
await sandbox.exec("echo Database: $DB_URL", {
env: { DB_URL: "postgresql://localhost:5432/mydb" }
});
Environment variables are merged with the sandbox’s existing environment, with your custom values taking precedence:
// Start a Node.js server with environment configuration
await sandbox.exec("node server.js >/tmp/server.log 2>&1", {
background: true,
env: {
NODE_ENV: "production",
PORT: "8080",
DATABASE_URL: "postgres://user:pass@db:5432/app"
}
});
// Run Python script with custom environment
await sandbox.exec("python app.py", {
env: {
PYTHONPATH: "/app/lib",
FLASK_ENV: "development",
SECRET_KEY: "my-secret-key"
}
});
Environment variables are only set for the specific command execution. To persist environment variables across multiple commands, either:
- Pass the same
env object to each exec() call
- Use shell export syntax:
export VAR=value && command
- Write them to a file and source it:
source /tmp/env.sh && command
Working directory
Set a persistent working directory for commands:
sandbox.cwd("/app/project");
// Subsequent commands run in /app/project
await sandbox.exec("bun install");
await sandbox.exec("bun test");
// Override for a single command
await sandbox.exec("ls", { cwd: "/tmp" });
// Get current directory
console.log(sandbox.getCwd()); // "/app/project"
Working directory persists across commands and affects exec() and file operations.
Pass data to stdin:
await sandbox.exec("cat > /tmp/input.txt", {
stdin: "line 1\nline 2\nline 3\n"
});
// Or pipe data
await sandbox.exec("python process.py", {
stdin: JSON.stringify({ data: [1, 2, 3] })
});
Background processes
Run long-lived processes without blocking:
// Start server in background
await sandbox.exec("python server.py >/tmp/server.log 2>&1", {
background: true
});
// Command returns immediately
// Server continues running
Real-time output streaming
Stream command output with callbacks:
await sandbox.exec("npm install && npm test", {
onStdout: (line) => console.log(line),
onStderr: (line) => console.error(line)
});
// Stream logs from background process
await sandbox.exec("python server.py 2>&1", {
background: true,
onStdout: (line) => {
if (line.includes("ERROR")) {
console.error("Server error:", line);
}
}
});
Callbacks receive output line-by-line as commands execute.
Managing background processes
Check if a process is running:
// Find PID
const pid = await sandbox.exec("pgrep -f server.py || echo 'not running'");
console.log("PID:", pid.stdout.trim());
// Check with ps
const ps = await sandbox.exec("ps aux | grep server.py | grep -v grep");
console.log(ps.stdout);
Read logs from background processes:
// Tail recent logs
const tail = await sandbox.exec("tail -n 100 /tmp/server.log");
console.log(tail.stdout);
// Follow logs (with timeout)
const follow = await sandbox.exec("timeout 5 tail -f /tmp/server.log", {
timeoutMs: 6_000
});
Stop a background process:
// Kill by name
await sandbox.exec("pkill -f server.py");
// Kill by PID
await sandbox.exec("kill 1234");
// Force kill
await sandbox.exec("pkill -9 -f server.py");
Error handling
const result = await sandbox.exec("ls /nonexistent");
if (!result.success) {
console.error("Command failed:");
console.error("Exit code:", result.exitCode);
console.error("Error output:", result.stderr);
}
Common patterns
Install dependencies and run
await sandbox.exec("npm install", { timeoutMs: 60_000 });
await sandbox.exec("npm test", {
timeoutMs: 30_000,
env: { NODE_ENV: "test", CI: "true" }
});
Start server and wait for readiness
// Start server
await sandbox.exec("node server.js >/tmp/server.log 2>&1", {
background: true
});
// Wait for port to be ready
for (let i = 0; i < 10; i++) {
const check = await sandbox.exec("nc -z localhost 3000 && echo ready || echo waiting");
if (check.stdout.includes("ready")) {
break;
}
await new Promise(r => setTimeout(r, 1000));
}
const host = sandbox.expose(3000);
console.log(`Server ready at https://${host}`);
Capture output to file
// Run and capture both stdout and stderr
await sandbox.exec("python script.py > /tmp/output.log 2>&1");
// Read the output
const output = await sandbox.files.read("/tmp/output.log");
console.log(output);
Capture stderr for debugging
To capture error messages when commands fail silently, redirect stderr:
// Redirect stderr to stdout and capture to file
await sandbox.exec("python script.py 2>&1 | tee /tmp/output.log");
// Read captured output
const logs = await sandbox.files.read("/tmp/output.log");
console.log(logs);
Conditional execution
// Run only if file exists
await sandbox.exec("test -f /app/config.json && node app.js || echo 'Config missing'");
// Chain with error handling
await sandbox.exec("npm test || (echo 'Tests failed' && exit 1)");
Using environment variables with background processes
Combine env with background for configurable long-running services:
// Start a web server with custom configuration
await sandbox.exec("node server.js >/tmp/server.log 2>&1", {
background: true,
env: {
NODE_ENV: "production",
PORT: "3000",
API_KEY: process.env.MY_API_KEY, // Pass through from your environment
DATABASE_URL: "postgresql://localhost/mydb",
LOG_LEVEL: "info"
}
});
// Give the server time to start
await new Promise(r => setTimeout(r, 2000));
// Expose the port
const host = sandbox.expose(3000);
console.log(`Server running at https://${host}`);
// Check server logs if needed
const logs = await sandbox.exec("tail -n 50 /tmp/server.log");
console.log(logs.stdout);
Best practices
- Set timeouts: Always specify
timeoutMs for potentially long operations
- Use env option: Prefer the
env option over inline export commands for cleaner, more maintainable code
- Redirect output: For background processes, redirect to log files (
>/tmp/log 2>&1)
- Check success: Always check
result.success before assuming command worked
- Use background for servers: Don’t block on long-running processes
- Clean up processes: Kill background processes before terminating sandbox
- Escape carefully: Be mindful of shell escaping when passing user input
- Secure secrets: Use the
env option to pass sensitive data instead of embedding it in command strings
Limitations
- Max timeout per command: 60 seconds
- Background processes continue until sandbox stops or you kill them
- No interactive TTY support
- Commands run as root inside the container