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 withCREATEOS_SANDBOX_BASE_URL - Auth: API key via the
apiKeyoption orCREATEOS_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)— writesdatato an absolute guest path.dataisBodyInit: astring,Uint8Array/Buffer,Blob, orReadableStream.download(path)— reads a guest file and returns anArrayBuffer.
Both methods accept an optional RequestOptions third argument
(timeoutMs, signal, etc.).
Upload: text and binary
TypeScript1import { CreateosSandboxClient } from "@nodeops-createos/sandbox";23const client = new CreateosSandboxClient();4const sandbox = await client.createSandbox({ shape: "s-4vcpu-4gb", rootfs: "devbox:1" });56try {7 // Text — a string is valid BodyInit.8 await sandbox.files.upload("/tmp/hello.sh", "#!/bin/sh\necho hello\n");910 // 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:
TypeScript1await 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:
TypeScript1// Read as text2const buf = await sandbox.files.download("/tmp/result.txt");3console.log(new TextDecoder().decode(buf));45// Save binary artifact to disk6import { 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.
TypeScript1import { CreateosSandboxClient } from "@nodeops-createos/sandbox";2import { readFile, writeFile } from "node:fs/promises";34const client = new CreateosSandboxClient();5const sandbox = await client.createSandbox({ shape: "s-4vcpu-4gb", rootfs: "devbox:1" });67try {8 // 1. Upload the processing script.9 const script = await readFile("./process.py");10 await sandbox.files.upload("/tmp/process.py", script);1112 // 2. Upload the input data.13 const input = await readFile("./data.csv");14 await sandbox.files.upload("/tmp/data.csv", input);1516 // 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 }2122 // 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:
TypeScript1import { execFile } from "node:child_process";2import { promisify } from "node:util";3import { readFile, writeFile } from "node:fs/promises";45const run = promisify(execFile);67// Pack on the host.8await run("tar", ["-czf", "/tmp/bundle.tar.gz", "-C", "./src", "."]);9const archive = await readFile("/tmp/bundle.tar.gz");1011// 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.