Files
The examples on this page assume a Boxer server running at http://localhost:8080. See the installation guide to get one running.
Boxer has a server-side file store for passing data in and out of containers. The flow is:
- Upload input files before the run (
POST /files) - Reference them by path in the run request - Boxer bind-mounts each one read-only at
/<path>inside the container - Write output to
/output/inside the container - Download captured output files after the run (
GET /files?path=output/<exec_id>/...)
Setup
- Python
- TypeScript
uv init
uv add boxer-sdk
from boxer import BoxerClient
client = BoxerClient("http://localhost:8080")
pnpm init
pnpm add boxer-sdk
import { BoxerClient } from "boxer-sdk";
const client = new BoxerClient({ baseUrl: "http://localhost:8080" });
Uploading input files
First, create the script you want to run inside the container:
import json, os
os.makedirs('/output', exist_ok=True)
with open('/output/result.json', 'w') as f:
json.dump({'answer': 42}, f)
print('done')
Then upload it:
- Python
- TypeScript
with open("script.py", "rb") as f:
client.upload_file("workspace/script.py", f)
import { readFile } from "node:fs/promises";
const script = await readFile("script.py");
await client.uploadFile("workspace/script.py", script);
The path you provide (workspace/script.py) is the key in the store and the mount destination inside the container (/workspace/script.py).
You can upload a whole directory by iterating over it:
- Python
- TypeScript
import pathlib
root = pathlib.Path("project/")
for p in root.rglob("*"):
if p.is_file():
rel = str(p)
with open(p, "rb") as f:
client.upload_file(rel, f)
import { uploadPath } from "boxer-sdk/node";
// Upload a single file
const filePaths = await uploadPath(client, "./script.py");
// => ["script.py"]
// Upload an entire directory
const dirPaths = await uploadPath(client, "./project", "project");
// => ["project/main.py", "project/utils.py", ...]
Referencing files in a run
Pass the paths you uploaded in the files array. Boxer bind-mounts each file read-only at /<path> inside the container and checks that all listed files exist before spawning — the request fails with 400 if any are missing. The container cannot modify uploaded files; if the workload needs to write, have it write to /output/ instead.
Capturing output
Any path written under /output/ inside the container is captured to the file store automatically at the end of the run, keyed as output/<exec_id>/<relative-path>. Pass persist=True to keep the files available for download after the run returns:
- Python
- TypeScript
result = client.run(
image="python:3.12-slim",
cmd=["python3", "/workspace/script.py"],
files=["workspace/script.py"],
persist=True,
)
data = client.download_file(f"output/{result.exec_id}/result.json")
print(data) # b'{"answer": 42}'
const result = await client.run(
"python:3.12-slim",
["python3", "/workspace/script.py"],
{ files: ["workspace/script.py"], persist: true },
);
const data = await client.downloadFile(`output/${result.exec_id}/result.json`);
console.log(new TextDecoder().decode(data)); // {"answer": 42}
The /output/ directory itself exists inside the container - you do not need to create it, though os.makedirs('/output', exist_ok=True) is harmless.
Persistence
By default, both input files and output files are deleted once the response is returned. Set persist=True to keep them:
- Python
- TypeScript
result = client.run(
image="python:3.12-slim",
cmd=["python3", "/workspace/script.py"],
files=["workspace/script.py"],
persist=True,
)
# Files are still available after the run:
out = client.download_file(f"output/{result.exec_id}/result.json")
inp = client.download_file("workspace/script.py")
const result = await client.run(
"python:3.12-slim",
["python3", "/workspace/script.py"],
{ files: ["workspace/script.py"], persist: true },
);
// Files are still available after the run:
const out = await client.downloadFile(`output/${result.exec_id}/result.json`);
const inp = await client.downloadFile("workspace/script.py");
Use persist=True when:
- You want to download output files separately after inspecting
stdout/stderr - You are running a batch of tasks and want to collect all outputs at the end
Sharing files across runs
Because files are stored by path, you can upload once and reuse across multiple runs:
- Python
- TypeScript
with open("model_weights.bin", "rb") as f:
client.upload_file("shared/weights.bin", f)
with open("run.py", "rb") as f:
client.upload_file("run.py", f)
for prompt in prompts:
result = client.run(
image="myimage:latest",
cmd=["python3", "/run.py", prompt],
files=["shared/weights.bin", "run.py"],
persist=True, # keep weights for next iteration
)
import { readFile } from "node:fs/promises";
const weights = await readFile("model_weights.bin");
await client.uploadFile("shared/weights.bin", weights);
const runScript = await readFile("run.py");
await client.uploadFile("run.py", runScript);
for (const prompt of prompts) {
const result = await client.run(
"myimage:latest",
["python3", "/run.py", prompt],
{ files: ["shared/weights.bin", "run.py"], persist: true },
);
}
Full example
See the upload-and-run example for a complete walkthrough: uploading a Python project, running its test suite inside the sandbox, and reading the results.