NodeOps
UK

How-to: move files in and out of a sandbox

Push files into a running sandbox and pull artifacts back out with the SDK's sandbox.files API.

At a glance

  • Package: @nodeops-createos/sandbox (npm)
  • Import: import { createClient } from "@nodeops-createos/sandbox"
  • Base URL: https://api.sb.createos.sh (override with CREATEOS_SANDBOX_BASE_URL)
  • Auth: API key via the apiKey option or CREATEOS_SANDBOX_API_KEY

Problem

You need to push code, data, or configuration into a running sandbox and pull artifacts back out: a script to run, a PDF to process, a generated report to save locally.

Solution

File transfer lives on sandbox.files, a SandboxFiles instance scoped to that sandbox.

  • upload(path, data): writes data to an absolute guest path. data is BodyInit: a string, Uint8Array / Buffer, Blob, or ReadableStream.
  • download(path): reads a guest file and returns an ArrayBuffer.

Both methods accept an optional RequestOptions third argument (timeoutMs, signal, etc.).

Upload: text and binary

TypeScript
1import { createClient } from "@nodeops-createos/sandbox";
2
3const client = createClient();
4const sandbox = await client.createSandbox({ shape: "s-4vcpu-4gb", rootfs: "devbox:1" });
5
6try {
7 // Text — a string is valid BodyInit.
8 await sandbox.files.upload("/tmp/hello.sh", "#!/bin/sh\necho hello\n");
9
10 // Binary — pass a Uint8Array or Buffer.
11 import { readFile } from "node:fs/promises";
12 const bytes = await readFile("/local/data/input.bin");
13 await sandbox.files.upload("/tmp/input.bin", bytes);
14} finally {
15 await sandbox.destroy();
16}

Guest paths must be absolute. Parent directories must already exist; create them first if needed:

TypeScript
1await sandbox.runCommand("mkdir", ["-p", "/opt/myapp/data"]);
2await sandbox.files.upload("/opt/myapp/data/config.json", configJson);

Download: text and binary

download always returns an ArrayBuffer. Decode it with TextDecoder for text, or pass it straight to writeFile / Buffer.from for binary:

TypeScript
1// Read as text
2const buf = await sandbox.files.download("/tmp/result.txt");
3console.log(new TextDecoder().decode(buf));
4
5// Save binary artifact to disk
6import { writeFile } from "node:fs/promises";
7const imgBuf = await sandbox.files.download("/tmp/output.png");
8await writeFile("output.png", Buffer.from(imgBuf));

End-to-end recipe: upload → run → download

Upload a script, run it, pull back the output file it wrote.

TypeScript
1import { createClient } from "@nodeops-createos/sandbox";
2import { readFile, writeFile } from "node:fs/promises";
3
4const client = createClient();
5const sandbox = await client.createSandbox({ shape: "s-4vcpu-4gb", rootfs: "devbox:1" });
6
7try {
8 // 1. Upload the processing script.
9 const script = await readFile("./process.py");
10 await sandbox.files.upload("/tmp/process.py", script);
11
12 // 2. Upload the input data.
13 const input = await readFile("./data.csv");
14 await sandbox.files.upload("/tmp/data.csv", input);
15
16 // 3. Run the script; it writes its output to /tmp/report.json.
17 const { result } = await sandbox.runCommand("python3", ["/tmp/process.py"]);
18 if (result.exit_code !== 0) {
19 throw new Error(`script failed (exit ${result.exit_code}):\n${result.stderr}`);
20 }
21
22 // 4. Download the artifact.
23 const report = await sandbox.files.download("/tmp/report.json");
24 await writeFile("report.json", Buffer.from(report));
25 console.log("report.json written locally");
26} finally {
27 await sandbox.destroy();
28}

See the streaming how-to if you want to watch stdout / stderr while the script runs instead of waiting for it to exit.

Bulk transfers: tar inside, unpack with runCommand

The SDK does one-shot transfers: upload and download move one file per call. There is no directory mirror or watch mode. For bulk input, pack a directory into an archive on the host, upload the archive, and unpack it inside the guest:

TypeScript
1import { execFile } from "node:child_process";
2import { promisify } from "node:util";
3import { readFile, writeFile } from "node:fs/promises";
4
5const run = promisify(execFile);
6
7// Pack on the host.
8await run("tar", ["-czf", "/tmp/bundle.tar.gz", "-C", "./src", "."]);
9const archive = await readFile("/tmp/bundle.tar.gz");
10
11// Upload and unpack inside the sandbox.
12await sandbox.files.upload("/tmp/bundle.tar.gz", archive);
13await sandbox.runCommand("mkdir", ["-p", "/opt/app"]);
14await sandbox.runCommand("tar", ["-xzf", "/tmp/bundle.tar.gz", "-C", "/opt/app"]);

The same pattern works in reverse: tar an output directory inside the guest, download the archive, and unpack locally.

Relative vs absolute guest paths

The API requires absolute guest paths (starting with /). Relative paths like script.py are rejected with a validation error. Use /tmp for ephemeral files; for anything you need to persist across a resize or across the sandbox lifetime, mount a disk and target its mount point instead.


Reference: SandboxFiles: full method signatures and error types.

100,000+ Builders. One Platform.

Get product updates, builder stories, and early access to features that help you ship faster.

NodeOps is the agentic operating system for production AI. CreateOS is its flagship product.