Skip to content

Use cases

Short, concrete starting points for each MicroVM 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.

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: MicroVM > Images > Build image, choose Dockerfile, pick sandbox-base as the base image (required so the image 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>"
}'

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.

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",
"source": {"image": "registry.example.com/acme/internal-api:latest"},
"auth": {"kind": "registry", "username": "ci", "password": "<registry password>"},
"location_id": "<location id>"
}'

Git-built service image with runtime defaults

Section titled “Git-built service image 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",
"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 MicroVM 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 MicroVM, 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 MicroVM returns the address to Allocated on your account rather than releasing it back to the pool, so the next MicroVM 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 MicroVM 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 first job of the day is not slower than the rest.

Panel: MicroVM > Connectors > Add connector, choose the GitHub App card, pick the installed GitHub account, set Labels to microvm, Max concurrent to 5 and Warm pool size to 1. In your workflow: runs-on: [self-hosted, microvm].

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": ["microvm"],
"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 MicroVMs API (see Preview environment above) to publish the result once the build finishes.

Two teams share one account but should not be able to break each other’s sandboxes. Create one MicroVM API key per team from MicroVM > API keys > Create key, named for the team, and give each team only its own key.

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 first, 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>"