# How to run a background process in a sandbox Call `spawn()` with the command. It starts a server, watcher or REPL, returns immediately, and keeps running after you disconnect. **On Runtime, a background process outlives the connection that started it. A paused sandbox keeps the process running across the pause.** Reconnect from another process, and get the process back by its ID, with its output from the start. A sandbox whose server sits waiting costs $0.03125 an hour at 2 vCPU and 4 GiB. With both CPUs busy, it costs $0.08 an hour ([pricing](/docs/pricing), checked 26 October 2026). ## Start a server and a REPL ```ts import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); const server = await sbx.spawn("python3 -m http.server 8000", { cwd: "/workspace" }); console.log(server.id, server.info.state); const repl = await sbx.spawn(["python3", "-i", "-q"], { stdin: "pipe" }); await repl.write("print(21 * 2)\n"); await repl.write("exit()\n", { eof: true }); console.log((await repl.wait()).stdout); for (const p of await sbx.processes.list()) console.log(p.id, p.state, p.command); await server.kill("SIGTERM"); ``` ```python from withruntime import Sandbox with Sandbox.create() as sbx: server = sbx.spawn("python3 -m http.server 8000", cwd="/workspace") print(server.id, server.info["state"]) repl = sbx.spawn(["python3", "-i", "-q"], stdin="pipe") repl.write("print(21 * 2)\n") repl.write("exit()\n", eof=True) print(repl.wait().stdout) for process in sbx.processes(): print(process["id"], process["state"], process["command"]) server.kill("SIGTERM") ``` A string runs under `bash -c`. A list runs the program with no shell. `stdin: "pipe"` keeps the process's input open so `write()` can feed it, and `eof` closes it. ## From the command line `spawn` prints the process ID. `ps` lists processes. `logs` prints what a process has written so far, then returns, even while the process runs: ```bash id=$(runtime sandboxes create) pid=$(runtime sandboxes spawn "${id}" -- python3 -m http.server 8000) runtime sandboxes ps "${id}" runtime sandboxes logs "${id}" "${pid}" runtime sandboxes kill "${id}" "${pid}" runtime sandboxes stop "${id}" ``` ```bash no-run runtime sandboxes logs "${id}" "${pid}" -f # follow until the process exits ``` An agent over MCP calls `runtime_sandboxes_exec` with `"background": true`. Then it calls `runtime_sandboxes_process` with `"action": "read"` to follow the output ([MCP](/docs/mcp)). ## Get a process back later A process does not belong to the script that started it. Save its ID. Any later process can reconnect and read the output from where it left off: ```ts check import { Sandbox } from "withruntime"; const sbx = await Sandbox.connect(process.env.SANDBOX_ID!); const job = await sbx.processes.get(process.env.PROCESS_ID!); for await (const event of job.output({ cursor: 0 })) { if (event.type === "stdout") process.stdout.write(event.data); if (event.type === "exit") console.log("exit", event.exitCode); } ``` ```python check import os from withruntime import Sandbox sbx = Sandbox.connect(os.environ["SANDBOX_ID"]) job = sbx.process(os.environ["PROCESS_ID"]) for event in job.output(cursor=0): if event["type"] == "stdout": print(event["data"], end="") ``` `output()` yields every event from the start, or from a `cursor`, until the process exits. ## What a process gets | Setting or limit | Value | | ---------------- | ------------------------------------------------------------------------- | | Returns | Immediately, with the process ID and its state | | Input | `stdin: "pipe"` (Python `stdin="pipe"`) for `write()`; `eof` closes it | | Signals | `kill("SIGTERM")` or any signal name; `POST …/processes/{id}:signal` | | Output kept | The latest 1 MiB of each process's output, in the sandbox | | Across a pause | Running processes are kept with their memory, and continue after the wake | | Lifetime | Until it exits, is killed, or the sandbox stops | | Terminal | Add `pty: { cols, rows }` for a program that needs one | | Cost | No charge per process; the sandbox's measured CPU and reserved memory | ## Mistakes to avoid - **Starting a server with `exec`.** A process started by `exec` ends when its command ends, and `exec` waits for it. Start a server, a file watcher or a queue worker with `spawn`. - **Printing more than 1 MiB and reading it later.** A reader that was away while more than that came out gets a `truncated` event. The event says how many bytes are gone. `wait()` sets both truncation flags. For a process with a lot of output, send the output to a file and read the file. - **Expecting it to run with nothing happening.** A running process keeps its sandbox running, with no time limit. But a server that waits with no request, traffic or CPU use is idle. The sandbox pauses itself after `idlePauseSeconds`, and a request wakes it. For a server that must respond all day without a wake, see [keep a sandbox running](/how-to/keep-a-sandbox-running). - **Opening a port with nothing listening.** Before you share a port with a preview or `runtime sandboxes port-forward`, check that `processes.list()` shows the server as running. ## Related - [Stream command output](/how-to/stream-command-output) for commands that end. - [Open an interactive terminal](/how-to/open-an-interactive-terminal) for a shell you type into. - [Preview agent-built apps](/use-cases/preview-agent-built-apps): a spawned dev server, shared at an HTTPS address. - [Background processes](/docs/javascript#background-processes) in the SDK reference. Facts on this page were checked on 26 October 2026.