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 { CreateosSandboxClient } from "@nodeops-createos/sandbox";
2
3const client = new CreateosSandboxClient();
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 { CreateosSandboxClient } from "@nodeops-createos/sandbox";
2import { readFile, writeFile } from "node:fs/promises";
3
4const client = new CreateosSandboxClient();
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 Workspace.

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

CreateOS is a unified intelligent workspace where ideas move seamlessly from concept to live deployment, eliminating context-switching across tools, infrastructure, and workflows with the opportunity to monetize ideas immediately on the CreateOS Marketplace.