Sandboxes
How project sandboxes run in Docker, on Blaxel or on Runtime Cloud, what's inside them, and how to maintain them.
How project sandboxes run in Docker, on Blaxel or on Runtime Cloud, what's inside them, and how to maintain them.
Each project runs in its own sandbox. Locally that’s a Docker container made from the zap-sandbox image. In production it’s a microVM made from the same image, on Blaxel or Runtime Cloud.
The image is defined in docker/sandbox/:
ttyd, the web terminal behind the Shell tabstart.sh, which keeps three processes running: the app’s dev server (or a placeholder page), the host-rewriting proxy, and the terminalforwarder.mjs, which runs an agent task and streams its events back to OneDropdb.php, which finds the app’s databases and runs the queries behind Tools → Databaseauth.php and guides/auth.md, behind Tools → Users & Auth: the tool that lists the app’s users and saves provider keys, and the guide the agent follows to add sign-ininstructions.md, the rules the agent follows (plain language, how to start a dev server, default stacks)Build or rebuild it with:
php artisan sandbox:build-image
Each container gets:
127.0.0.1 only: the proxy (preview) and the terminalzap.sandbox label, so you can find all sandboxesBlaxel runs each sandbox in its own microVM. OneDrop talks to its REST APIs, so the server needs no Docker.
In the Blaxel console, create an API key. Set it as BL_API_KEY, and your workspace name as BL_WORKSPACE, in your secret manager, with SANDBOX_PROVIDER=blaxel. Set SANDBOX_CALLBACK_URL to your public APP_URL, and leave SANDBOX_GATEWAY_DOMAIN empty.
Install the Blaxel CLI (bl), then run:
php artisan sandbox:build-image
With SANDBOX_PROVIDER=blaxel, this builds docker/sandbox on Blaxel with the steps in docker/sandbox/blaxel/ appended: Blaxel’s sandbox API and an entrypoint that starts it, then start.sh as the sandbox user. The Docker image is unchanged. Set BLAXEL_CLI if bl isn’t on the PATH. Run it again after changing docker/sandbox/; existing sandboxes then update themselves.
How it differs from Docker:
PORT=80 in every process, so OneDrop passes the app’s port as ZAP_PORT and sets PORT again for each command.bl.run. Each link carries a token, and OneDrop hands it only to people allowed to see the project. The first visit trades the token for a cookie. Tokens last 7 days, and OneDrop fetches new links at most once a day when someone opens the project.BLAXEL_MEMORY_MIB (4096 by default, the most a new account allows) also sets CPUs, one per 2048 MB. About half of it holds the sandbox’s files.To find or delete sandboxes by hand:
bl get sandboxes
bl delete sandbox <name>
Runtime Cloud runs each sandbox in its own Firecracker microVM. OneDrop talks to its HTTPS API, so the server needs no Docker.
Create a key at API keys and set it as RUNTIME_API_KEY in your secret manager, with SANDBOX_PROVIDER=runtime. Set SANDBOX_CALLBACK_URL to your public APP_URL, and leave SANDBOX_GATEWAY_DOMAIN empty.
php artisan sandbox:build-image
With SANDBOX_PROVIDER=runtime, this builds docker/sandbox on Runtime as zap-sandbox:latest. It needs Node.js 22.12 or later. Run it again after changing docker/sandbox/; existing sandboxes then update themselves.
Sandboxes use the free trial until you set RUNTIME_FUNDING=paid. Add credit at billing before production traffic runs.
How it differs from Docker:
/home/sandbox/.zap-env, readable only by the sandbox user. start.sh and the Shell tab read it. They never appear in a command line.runtimehost.com. Each link carries a token, and OneDrop hands it only to people allowed to see the project. Tokens last 7 days, and OneDrop fetches new links at most once a day when someone opens the project.RUNTIME_PERSISTENT=true so leases renew.To find or stop sandboxes by hand, use the Runtime CLI with the same key:
npx withruntime sandbox ls
npx withruntime sandbox stop <id>
The agent creates an executable /workspace/.zap/dev script that starts the app’s dev server on $PORT, then runs /opt/zap/restart. The container’s loop starts that script instead of the placeholder. Output goes to /tmp/zap-server.log, which the Console tab shows.
The guides the agent follows, the tools behind panels like Database and Growth, and the proxy all ship in the sandbox image. After you change anything in docker/sandbox/, rebuild the image (php artisan sandbox:build-image; the server deploy script does this when those files change). Existing sandboxes then move to the new image on their own:
An update keeps the app’s files (/workspace), App Storage buckets, and the sandbox user’s home folder (/home/sandbox), which holds the agent’s conversation history and anything the agent installed there, such as a local database. Shell settings in the home folder stay as they were rather than taking the image’s. The app’s processes are paused while the files are copied, so a database is copied in a consistent state. The app restarts, and published projects are published again. Only one update runs per project at a time.
To update every outdated sandbox now, or just one project:
php artisan sandbox:update
php artisan sandbox:update 12
Projects the agent is working on are skipped; they update before their next run.
To replace a sandbox even when it’s up to date (for example, one that’s broken):
php artisan sandbox:recreate 12 --keep-files
--keep-files copies the app (/workspace), App Storage and the sandbox user’s home folder (with the agent’s conversation history) into the new sandbox. Without it, the project starts from an empty workspace. Published projects are published again automatically.
Nothing removes old containers automatically yet. To remove every sandbox and publish container:
docker rm -f $(docker ps -aq --filter label=zap.sandbox) $(docker ps -aq --filter label=zap.sandbox.publisher)
The normal test suite uses a fake provider. To test against real containers:
RUN_DOCKER_TESTS=1 vendor/bin/pest tests/Integration