Example repos

Two public repos under https://github.com/parallel-sandbox . Both run locally with docker compose up, and they are what we use to test the platform itself. The tool calls below are the current way to run them in a box.

example-compose-app

https://github.com/parallel-sandbox/example-compose-app

A Node API (port 3000) that queues jobs and a Go worker (port 8080) that completes them, wired together by docker-compose.yml. e2e/ holds a Playwright test that opens the page in a real browser, adds a job, waits for the worker to finish it and saves a screenshot.

In a box this exercises: git clone, docker compose up --build, service names that resolve inside the box, a headed browser on the virtual display, sandbox_shot recording, sandbox_get for the screenshot, sandbox_review to hand the result to the person.

sandbox_start { "name": "example-compose-app e2e", "goal": "Run the example-compose-app e2e test in a real browser on the box screen; done when it passes and the screenshot and the video are saved", "services": [{ "name": "api", "port": 3000, "web": true }, { "name": "worker", "port": 8080 }], "orientation": "landscape" }
sandbox_exec  { "id": "<id>", "cmd": "git clone https://github.com/parallel-sandbox/example-compose-app.git", "note": "clone the example repo" }
sandbox_exec  { "id": "<id>", "cmd": "docker compose up -d --build --wait", "cwd": "example-compose-app", "timeoutSec": 600, "note": "build and start the api and the worker" }
sandbox_exec  { "id": "<id>", "cmd": "curl -s http://api:3000/api/health", "note": "check the api answers by its name" }
sandbox_exec  { "id": "<id>", "cmd": "npm ci && npx playwright install chromium", "cwd": "example-compose-app/e2e", "timeoutSec": 600, "note": "install the e2e test dependencies" }
sandbox_shot  { "id": "<id>", "record": "start" }
sandbox_exec  { "id": "<id>", "cmd": "HEADED=1 BASE_URL=http://localhost:3000 npx playwright test", "cwd": "example-compose-app/e2e", "timeoutSec": 300, "note": "run the e2e test on the box screen" }
sandbox_shot  { "id": "<id>", "record": "stop" }
sandbox_get   { "id": "<id>", "path": "example-compose-app/e2e/screenshots/jobs.png" }
sandbox_review { "id": "<id>", "what": "Type a job title and press Add job: within a few seconds its status turns from pending to done with a sha256 result. I ran the e2e test on the box screen and recorded it under Screenshots & recordings.", "open": "web", "port": 3000 }

The flow ends with sandbox_review, not sandbox_stop: the box waits under Ready for you in the person's app, where its card opens the page and shows the recording and the steps' screenshots under Screenshots & recordings. Once a box is stopped, the app shows none of these (Finishing a box).

orientation: "landscape" gives the box a 1280 × 800 screen, so the desktop page the test opens fits on it; without it the screen is 390 × 844 portrait (sandbox_start).

api and worker each resolve inside the box to an address of their own from the start, api:3000 and worker:8080 reach the published ports, and the two containers also reach each other on the compose network, so there is nothing to wire.

The other way to run it: you changed api and a worker already runs on your staging at https://staging.example.com. Pass externalBaseUrl; both services then start external: api:3000 and worker:8080 forward HTTP there. Switch api to box and start only api (boxd does not hold its port, so either order works); worker keeps going to staging, path kept:

sandbox_start { "name": "api change against staging worker", "goal": "Test the changed api against the worker on staging; done when a job added through the changed api is completed by the staging worker", "services": [{ "name": "api", "port": 3000 }, { "name": "worker", "port": 8080 }], "externalBaseUrl": "https://staging.example.com" }
sandbox_exec  { "id": "<id>", "cmd": "git clone https://github.com/parallel-sandbox/example-compose-app.git", "note": "clone the example repo" }
sandbox_wire  { "id": "<id>", "service": "api", "mode": "box" }
sandbox_exec  { "id": "<id>", "cmd": "docker compose up -d --build --wait api", "cwd": "example-compose-app", "timeoutSec": 600, "note": "build and start only the api" }

example-web-with-logs

https://github.com/parallel-sandbox/example-web-with-logs

One static page with the browser log SDK @parallelsandbox/log attached. A button throws an uncaught error three function calls deep. The page is built with esbuild (minified, with source maps), served by nginx, and configured at container start from environment variables so the project's write key never enters the image.

In a box this exercises: secrets at sandbox_start, source map upload, a page on the virtual display, logs_errors returning the error with the stack resolved to src/app.js lines. The log project must allow the origin the page is opened from; here that is http://localhost:8080 inside the box (see Log SDK).

sandbox_start { "name": "example-web-with-logs", "goal": "Check that an error thrown on the page reaches the log service; done when logs_errors returns it with the stack resolved to src/app.js", "services": [{ "name": "web", "port": 8080, "web": true }], "secrets": ["PSBX_LOG_WRITE_KEY"], "orientation": "landscape" }
sandbox_exec  { "id": "<id>", "cmd": "git clone https://github.com/parallel-sandbox/example-web-with-logs.git", "note": "clone the example repo" }
sandbox_exec  { "id": "<id>", "cmd": "PSBX_RELEASE=$(git rev-parse --short HEAD) PSBX_LOG_PROJECT=<project id> PSBX_LOG_ENDPOINT=https://log.parallelsandbox.com docker compose up -d --build --wait", "cwd": "example-web-with-logs", "timeoutSec": 600, "note": "build and serve the page with its log settings" }

Upload the source maps from your own machine, not from the box, so your API key never enters it. Same commit, so the same release id:

git clone https://github.com/parallel-sandbox/example-web-with-logs.git && cd example-web-with-logs
npm ci && PSBX_RELEASE=$(git rev-parse --short HEAD) npm run build
PSBX_API_KEY=<api key> PSBX_LOG_PROJECT=<project id> ./scripts/upload-sourcemaps.sh

Stacks are resolved when an error arrives, so upload the maps before you click. Back in the box:

sandbox_exec  { "id": "<id>", "cmd": "chromium --no-sandbox --kiosk --window-size=1280,800 --user-data-dir=/tmp/chrome http://localhost:8080/", "background": true, "note": "open the page on the box screen" }
sandbox_shot  { "id": "<id>" }
sandbox_exec  { "id": "<id>", "cmd": "xdotool mousemove 640 300 click 1", "note": "click the button that throws" }
logs_errors   { "project": "<project id>", "since": "10m" }
sandbox_stop  { "id": "<id>" }

The Chromium flags and the click at 640 300 assume the landscape screen this box was started with. On a portrait box (390 × 844, the default), open Chromium with --force-device-scale-factor=0.78 --window-size=500,1082 --window-position=0,0 instead of --window-size=1280,800, and take the button's position from the screenshot (sandbox_exec).

A page opened at localhost carries no box id (the SDK takes it from the host of one of the box's URLs), so this query filters by time, not by boxId.

Using them as templates

Both repos are plain: a docker-compose.yml at the root, one directory per service with its own Dockerfile, tests in their own directory. Any repo shaped like that runs in a box the same way. The only ParallelSandbox-specific parts are the goal and the services list at sandbox_start (what the box is for; names and ports) and, if you want browser errors, the log SDK.