Use cases
Short, concrete starting points for each sandbox feature. Panel steps are given where the panel is the natural way to do the task; REST API examples use curl with your account’s bearer API token (Account > API Tokens) against https://<your panel host>/api/microvm/..., except where noted. The panel now says Sandboxes and Kits, but the REST API did not change: the paths still begin with /api/microvm/ and kits are still sent as image_id and /images.
Sandboxes (E2B API)
Section titled “Sandboxes (E2B API)”AI code interpreter
Section titled “AI code interpreter”An agent needs to run Python it just wrote and see the real output before answering. Create a sandbox per conversation, run the code, read stdout, kill it when the conversation ends.
from e2b import Sandbox
sbx = Sandbox.create("sandbox-base", metadata={"location_id": "<location id>"})result = sbx.commands.run("python3 -c \"print(sum(range(100)))\"")print(result.stdout)sbx.kill()Grading untrusted student code
Section titled “Grading untrusted student code”A coursework platform runs student-submitted code that must not see other students’ files or the internet. One sandbox per submission, killed immediately after, keeps a bad submission from affecting anything else.
sbx = Sandbox.create("sandbox-base", metadata={"location_id": "<location id>"}, timeout=30)sbx.files.write("/home/user/solution.py", submission_source)result = sbx.commands.run("python3 /home/user/solution.py < /home/user/input.txt", timeout=10)grade(result.stdout, expected_output)sbx.kill()Data-analysis agent over an uploaded CSV
Section titled “Data-analysis agent over an uploaded CSV”A user uploads a CSV and asks questions about it. Write the file into a sandbox once, then let the agent run as many pandas queries against it as it needs within one sandbox’s lifetime.
sbx = Sandbox.create("sandbox-base", metadata={"location_id": "<location id>"}, timeout=600)sbx.files.write("/home/user/data.csv", csv_bytes)result = sbx.commands.run("python3 -c \"import pandas as pd; print(pd.read_csv('/home/user/data.csv').describe())\"")print(result.stdout)# further commands.run() calls reuse the same sbx for follow-up questionssbx.kill()Browser-less scraping worker
Section titled “Browser-less scraping worker”A short-lived worker fetches and parses a handful of pages with plain HTTP, no browser needed. A tight timeout means an unresponsive site cannot pin the sandbox open.
sbx = Sandbox.create("sandbox-base", metadata={"location_id": "<location id>"}, timeout=60)result = sbx.commands.run("curl -s https://example.com | grep -o '<title>[^<]*'")print(result.stdout)sbx.kill()Custom toolchain kit from a Dockerfile
Section titled “Custom toolchain kit from a Dockerfile”Your agent’s code interpreter needs numpy, pandas and ffmpeg preinstalled so every sandbox boots ready to work, instead of installing them on every run.
Panel: Sandboxes > Kits > Build a kit, choose Dockerfile, pick sandbox-base as the base kit (required so the kit keeps envd), and write:
FROM sandbox-baseRUN apt-get update && apt-get install -y ffmpeg && pip install numpy pandasREST equivalent:
curl -s -X POST https://<your panel host>/api/microvm/images \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "name": "data-toolchain", "source_kind": "dockerfile", "source": {"dockerfile": "FROM sandbox-base\nRUN apt-get update && apt-get install -y ffmpeg && pip install numpy pandas\n"}, "base_image_id": "<sandbox-base image id>", "location_id": "<location id>" }'Import an internal container image as a kit
Section titled “Import an internal container image as a kit”You already build and push an image to your own registry as part of CI. Import it directly instead of re-describing it as a Dockerfile. Choose the base kit from the catalog; every create request names one.
curl -s -X POST https://<your panel host>/api/microvm/images \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "name": "internal-api", "source_kind": "oci", "base_image_id": "<ready platform base kit id>", "source": {"image": "registry.example.com/acme/internal-api:latest"}, "auth": {"kind": "registry", "username": "ci", "password": "<registry password>"}, "location_id": "<location id>" }'Git-built service kit with runtime defaults
Section titled “Git-built service kit with runtime defaults”A small internal service builds from a Git repository on every release and always needs the same three environment variables and a run lifecycle hook that starts the process on port 8080.
curl -s -X POST https://<your panel host>/api/microvm/images \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "name": "internal-service", "source_kind": "git", "base_image_id": "<ready platform base kit id>", "source": {"repo": "https://github.com/acme/internal-service", "branch": "main"}, "location_id": "<location id>", "env": "{\"NODE_ENV\":\"production\",\"PORT\":\"8080\"}", "lifecycle_hooks": {"port": 8080, "run": {"enabled": true, "timeout": 30, "payload": "npm start"}} }'Sandboxes
Section titled “Sandboxes”Per-tenant worker with a lifetime cap
Section titled “Per-tenant worker with a lifetime cap”A SaaS backend spins up one sandbox per customer job with a hard 2-hour ceiling so a runaway job cannot bill indefinitely; when it hits the cap it pauses rather than losing work.
curl -s -X POST https://<your panel host>/api/microvm/vms \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "location_id": "<location id>", "image_id": "<image id>", "name": "job-4821", "max_lifetime_seconds": 7200, "on_timeout": "pause" }'Preview environment with HTTP ingress and a custom domain
Section titled “Preview environment with HTTP ingress and a custom domain”Every pull request gets its own sandbox, reachable at pr-142.preview.example.com once you point a CNAME at the generated hostname.
curl -s -X POST https://<your panel host>/api/microvm/vms \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "location_id": "<location id>", "image_id": "<image id>", "name": "pr-142", "ingress": {"http": {"enabled": true, "port": 3000}}, "domain": "pr-142.preview.example.com" }'Public sandbox on a reserved static IP
Section titled “Public sandbox on a reserved static IP”A service needs the same public address every time it redeploys, instead of a fresh IPv4 on every create. Reserve a static IP in the location once, then map it on create.
curl -s -X POST https://<your panel host>/api/microvm/vms \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "location_id": "<location id>", "image_id": "<image id>", "name": "fixed-ip-svc", "network": [{"kind": "public", "static_ip_id": "<static ip id>"}] }'Killing or deleting this sandbox returns the address to Allocated on your account rather than releasing it back to the pool, so the next sandbox you map it to gets the same address. See Networking.
Always-on webhook receiver
Section titled “Always-on webhook receiver”A small service must never idle-pause between webhook deliveries that can be hours apart, and should come back on its own if it ever crashes.
curl -s -X POST https://<your panel host>/api/microvm/vms \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "location_id": "<location id>", "image_id": "<image id>", "name": "webhook-receiver", "always_on": true, "ingress": {"http": {"enabled": true, "port": 8080, "health_path": "/health"}} }'Scheduled batch job via the REST API
Section titled “Scheduled batch job via the REST API”A nightly report generator: your own scheduler calls the create endpoint once a night, waits for the run to finish, reads the log, then deletes the sandbox instead of leaving it around.
VM=$(curl -s -X POST https://<your panel host>/api/microvm/vms \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{"location_id":"<location id>","image_id":"<batch image id>","name":"nightly-report","max_lifetime_seconds":1800,"on_timeout":"kill"}' \ | jq -r '.microvm.id')
sleep 60 && curl -s https://<your panel host>/api/microvm/vm/$VM/logs -H "Authorization: Bearer <token>"curl -s -X DELETE https://<your panel host>/api/microvm/vm/$VM -H "Authorization: Bearer <token>"Connectors and CI runners
Section titled “Connectors and CI runners”Ephemeral GitHub Actions runner
Section titled “Ephemeral GitHub Actions runner”Jobs on self-hosted need somewhere isolated to run instead of a shared long-lived runner. A connector with a warm pool of 1 keeps a runner ready so the morning job starts as quickly as later ones.
Panel: Sandboxes > Connectors > Add connector, choose the GitHub App card, pick the installed GitHub account, set Labels to sandbox, Max concurrent to 5 and Warm pool size to 1. In your workflow: runs-on: [self-hosted, sandbox].
GitLab runner
Section titled “GitLab runner”The same pattern for a GitLab project, authenticated with a runner token instead of an app install.
curl -s -X POST https://<your panel host>/api/microvm/connectors/gitlab \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "name": "gitlab-runner", "gitlab_url": "https://gitlab.com", "gitlab_token": "glrt-xxxxxxxxxxxxxxxxxxxx", "gitlab_tag_list": ["sandbox"], "location_id": "<location id>", "max_concurrent": 5, "warm_count": 1 }'PR preview builds
Section titled “PR preview builds”Reuse a connector’s warm pool for a build-and-deploy job that also opens an HTTP port, then let the job itself call the sandboxes API (see Preview environment above) to publish the result once the build finishes.
API keys
Section titled “API keys”Per-team keys
Section titled “Per-team keys”Create a named sandbox API key for each team or integration from Sandboxes > API keys > Create key, so each one can be revoked independently. Keys that belong to the same account share access to that account’s sandboxes. When teams need isolation from each other, give each team its own account.
Dedicated CI key with revocation
Section titled “Dedicated CI key with revocation”A pipeline needs a key that is easy to identify and to cut off without touching any human’s key.
curl -s -X POST https://<your panel host>/api/microvm/api-keys \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{"name": "ci-pipeline"}'# {"success":true,"key":{"id":"...","name":"ci-pipeline","prefix":"vc_sb_..."},"plaintext":"vc_sb_..."}# store .plaintext now (shown once); keep .key.id for revocation later.
curl -s -X DELETE https://<your panel host>/api/microvm/api-key/<key id> \ -H "Authorization: Bearer <token>"Key rotation
Section titled “Key rotation”Create the replacement key, update every consumer to use it, then revoke the old one, so there is no window where nothing works.
NEW=$(curl -s -X POST https://<your panel host>/api/microvm/api-keys \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{"name": "ci-pipeline-2026-09"}')echo "$NEW" | jq -r '.plaintext' # roll this out to every consumer, confirm it works, then:curl -s -X DELETE https://<your panel host>/api/microvm/api-key/<old key id> \ -H "Authorization: Bearer <token>"
