Skip to content
Runtime

How to upload a folder to a sandbox

Call sbx.files.upload(localDir, "/workspace/project"). The whole folder is sent as one compressed archive, and it keeps its permissions.

On Runtime a folder upload is one call and one archive, not a loop of file writes. Scripts stay runnable because each file keeps its mode, and single large files go in parallel 1 MiB chunks, each checked by SHA-256, resuming after a dropped connection. There is no size limit beyond the sandbox's disk.

Upload a project and run it

TypeScriptimport { mkdtemp, writeFile } from "node:fs/promises";import { tmpdir } from "node:os";import { join } from "node:path";import { Sandbox } from "withruntime";const project = await mkdtemp(join(tmpdir(), "project-"));await writeFile(join(project, "main.py"), "print('hi')\n");await writeFile(join(project, "run.sh"), "python3 main.py\n", { mode: 0o755 });await using sbx = await Sandbox.create();await sbx.files.upload(project, "/workspace/project");console.log(await sbx.files.list("/workspace/project"));const run = await sbx.exec("./run.sh", { cwd: "/workspace/project" });console.log(run.stdout);
Pythonimport pathlibimport tempfilefrom withruntime import Sandboxproject = pathlib.Path(tempfile.mkdtemp())(project / "main.py").write_text("print('hi')\n")with Sandbox.create() as sbx:    sbx.files.upload(str(project), "/workspace/project")    for entry in sbx.files.list("/workspace/project"):        print(entry["type"], entry["size"], entry["path"])    print(sbx.exec(["python3", "main.py"], cwd="/workspace/project").stdout)

From a terminal, cp takes a sandbox path as <id>:/path. It copies directories whole:

Terminalid=$(runtime sandboxes create)mkdir -p project && echo "print('hi')" > project/main.pyruntime sandboxes cp ./project "${id}:/workspace/project"runtime sandboxes files "${id}" /workspace --depth 2runtime sandboxes stop "${id}"

From any language, over HTTP

The SDKs and the CLI send the folder to one API route. A language with no SDK (PHP, Ruby, Go, a shell script) can do the same with a tar archive and a PUT. The sandbox unpacks the archive with its own tar as the bytes arrive. It keeps modes and links, and it creates the target folder:

Terminaltar -czf site.tar.gz -C ./site .curl -sS -X PUT "https://api.withruntime.com/v1/sandboxes/${ID}/files/archive?path=/workspace/site" \  -H "Authorization: Bearer ${RUNTIME_API_KEY}" --data-binary @site.tar.gz

One request carries up to 1 MiB of archive. A larger archive starts an upload with POST …/files/archive/uploads. Then send 1 MiB parts in order to PUT …/files/archive/uploads/{uploadId}?offset=. Finish with :commit, which returns once every file is in place. A part sent twice after a lost response is not written twice. Nothing is staged on the sandbox's disk. To download, use GET …/files/archive?path=/workspace/site&gzip=true. It streams an archive of any size (files in the API guide).

One file, or many small ones

Use files.write for a single file that you already hold in memory. It creates parent directories and replaces the file atomically. The mode option sets its permissions:

TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create();await sbx.files.write("/workspace/bin/start.sh", "#!/bin/sh\necho ready\n", { mode: 0o755 });await sbx.files.write("/workspace/data/input.csv", "a,b\n1,2\n");console.log(await sbx.files.exists("/workspace/data/input.csv"));
Pythonfrom withruntime import Sandboxwith Sandbox.create() as sbx:    sbx.files.write("/workspace/bin/start.sh", "#!/bin/sh\necho ready\n", mode=0o755)    sbx.files.write("/workspace/data/input.csv", "a,b\n1,2\n")    print(sbx.files.exists("/workspace/data/input.csv"))

For a whole tree, upload sends one archive instead of a write per file.

Sizes, speeds and costs

What Figure
How a folder travels One tar archive, unpacked by the sandbox as it arrives, permissions and links kept
Large single files Parallel 1 MiB chunks, each checked by SHA-256, resumed after a dropped connection
Size limit None beyond the disk; over HTTP, 1 MiB a request
Default disk 4 GiB, about 2.5 GiB free (measured 24 September 2026); ask for more with diskMiB
Disk without credit Up to 10 GiB
Disk speed At least about 40 MB/s, up to about 1 GB/s on a quiet server
Default mode 0o644 for write without mode; upload keeps each file's own
Cost Nothing per upload; the disk is part of the sandbox

Mistakes to avoid

  • Filling the default disk. The system image and installed packages share diskMiB. For a large repository or dataset, create the sandbox with Sandbox.create({ diskMiB: 16_384 }) (disk_mib in Python) (disk, CPU and memory).
  • Relative sandbox paths. SDK paths are absolute. /workspace is the sandbox user's home. sudo reaches the rest.
  • Uploading the same tree on every run. When every sandbox needs the same code or packages, bake them into a custom image once. Or keep data on a volume that outlives sandboxes.
  • A script that will not run. A file made with write and no mode gets 0o644. Pass mode: 0o755, or upload the folder, which keeps the mode it had.

Facts on this page were checked on 26 October 2026.

100 free hoursin your first 30 days.

of a 1 GB machine

  • No credit card
  • Four sandboxes at once, 2 vCPU and 4 GiB each
  • Then prepaid credit from $10, no plan fee
Start free, no card
Start free, no card