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.gzOne 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 withSandbox.create({ diskMiB: 16_384 })(disk_mibin Python) (disk, CPU and memory). - Relative sandbox paths. SDK paths are absolute.
/workspaceis the sandbox user's home.sudoreaches 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
writeand nomodegets 0o644. Passmode: 0o755, or upload the folder, which keeps the mode it had.
Related
- Download files from a sandbox to get results back.
- Watch files for changes while an agent edits the upload.
- Coding agent sandbox: check out or upload a repository, then let the agent work.
- Files in the SDK reference.
Facts on this page were checked on 26 October 2026.