Skip to content

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.

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()

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()

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 questions
sbx.kill()

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()

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-base
RUN apt-get update && apt-get install -y ffmpeg && pip install numpy pandas

REST equivalent:

Terminal window
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.

Terminal window
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.

Terminal window
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"}}
}'

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.

Terminal window
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.

Terminal window
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"
}'

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.

Terminal window
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.

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.

Terminal window
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"}}
}'

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.

Terminal window
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>"

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].

The same pattern for a GitLab project, authenticated with a runner token instead of an app install.

Terminal window
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
}'

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.

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.

A pipeline needs a key that is easy to identify and to cut off without touching any human’s key.

Terminal window
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>"

Create the replacement key, update every consumer to use it, then revoke the old one, so there is no window where nothing works.

Terminal window
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>"