Lifecycle¶
Create, connect, inspect, list and destroy sandboxes. Each sandbox is a Lambda MicroVM in your account, with a maximum lifetime of 8 hours.
When to use it¶
- Always: it is the starting point for everything else.
connect()when another process (a worker, a retry, another machine) has to keep working on the same sandbox.- Class-level
list()andkill()to clean up sandboxes nobody is using.
Quick example¶
from rayito import Sandbox
with Sandbox.create("rayito-base", timeout=1800, metadata={"run": "42"}) as sbx:
info = sbx.get_info()
print(info.sandbox_id, info.state, info.metadata) # ... RUNNING {'run': '42'}
print(sbx.is_running()) # True
for item in Sandbox.list(metadata={"run": "42"}): # (1)!
Sandbox.kill(item.sandbox_id)
- When the
withexits the sandbox is already dead: this loop finds nothing. It is the pattern for cleaning up orphaned sandboxes.
import asyncio
from rayito import AsyncSandbox
async def main() -> None:
sbx = await AsyncSandbox.create("rayito-base", timeout=1800, metadata={"run": "42"})
async with sbx:
info = await sbx.get_info()
print(info.sandbox_id, info.state, info.metadata)
print(await sbx.is_running())
for item in await AsyncSandbox.list(metadata={"run": "42"}):
await AsyncSandbox.kill(item.sandbox_id)
asyncio.run(main())
import { Sandbox } from "rayito";
{
await using sbx = await Sandbox.create({
template: "rayito-base",
timeoutMs: 1_800_000,
metadata: { run: "42" },
});
const info = await sbx.getInfo();
console.log(info.sandboxId, info.state, info.metadata);
console.log(await sbx.isRunning()); // true
}
for await (const item of Sandbox.list({ metadata: { run: "42" } })) {
await Sandbox.kill(item.sandboxId);
}
from rayito.e2b import Sandbox
with Sandbox.create("rayito-base", timeout=1800, metadata={"run": "42"}) as sbx: # (1)!
print(sbx.get_info().sandbox_id, sbx.is_running())
token = sbx.native.access_token # (2)!
for item in Sandbox.list().next_items(): # (3)!
print(item.sandbox_id, item.state)
- In the shim,
timeoutis E2B's logical deadline (300 s by default), not the platform lifetime: see Server-side deadline. - E2B has no such token; store it if another process is going to use
Sandbox.connect(sandbox_id, access_token=...). - An E2B
SandboxPaginator(next_items(),has_next,next_token).
Step by step¶
Create¶
Sandbox.create() launches a MicroVM (run-microvm) from an image and
waits for the agent and the kernel to be ready. The image comes, in order,
from the first argument (template), from RAYITO_TEMPLATE, or it fails.
It can be a name (rayito-base, resolved to the ARN in your account) or a
full ARN.
The sandbox size (CPU and memory) is a property of the image: memory is
fixed when it is published. size="4gb" (TypeScript: size: "4gb")
picks, without calling AWS, the already-published image of that size
(rayito-base-4gb); see Sizes and
Limits: size.
Destroy¶
kill() terminates the MicroVM (terminate-microvm) and returns True if
it found it. with (Python) and await using (TypeScript) call it on
exit; without them, call kill() yourself in a finally. The class form,
Sandbox.kill(sandbox_id), kills a sandbox for which you only have the id
(it does not need the access token). close() is something else: it
releases the handle's local resources (channels, token renewal thread) and
leaves the MicroVM alive.
A forgotten sandbox bills
It lives until its timeout (3600 s by default, cap 8 h) billing
compute (pricing). With the default
auto-suspend, a sandbox with no traffic is
suspended after 300 s and stops billing compute, but it keeps paying
for its snapshot storage.
Connect¶
Sandbox.connect(sandbox_id, access_token=...) opens a new handle on an
existing sandbox, from any process with AWS credentials. It needs the
sandbox's access token (sbx.access_token, or the
RAYITO_ACCESS_TOKEN variable), which is generated in create() and
cannot be recovered later: store it next to the id. If the sandbox was
suspended, connect() resumes it; with timeout= it also extends its
logical deadline.
On a handle you already have, sbx.connect() (TypeScript:
await sbx.connect()) reopens it the same way: it resumes if needed,
waits for the agent and returns the same object. It is the way to continue
after a pause() (Pause and resume).
from rayito import Sandbox
sbx = Sandbox.create()
sandbox_id, token = sbx.sandbox_id, sbx.access_token # store them together
sbx.close() # (1)!
again = Sandbox.connect(sandbox_id, access_token=token)
try:
print(again.commands.run("echo still alive").stdout)
finally:
again.kill()
- Releases the local handle; the sandbox keeps running.
import asyncio
from rayito import AsyncSandbox
async def main() -> None:
sbx = await AsyncSandbox.create()
sandbox_id, token = sbx.sandbox_id, sbx.access_token
await sbx.close()
again = await AsyncSandbox.connect(sandbox_id, access_token=token)
async with again:
print((await again.commands.run("echo still alive")).stdout)
asyncio.run(main())
import { Sandbox } from "rayito";
const sbx = await Sandbox.create();
const { sandboxId, accessToken } = sbx; // store them together
sbx.close(); // releases the local handle; the sandbox keeps running
await using again = await Sandbox.connect(sandboxId, { accessToken });
console.log((await again.commands.run("echo still alive")).stdout);
Inspect¶
get_info() returns a SandboxInfo: sandbox_id, state
(RUNNING, SUSPENDED…), endpoint, template, template_version,
started_at, metadata, lifecycle, agent_version, cpu_count and
memory_mb (plus size if you used size=). expires_at is the current
deadline, platform_expires_at the platform cap and remaining_seconds()
what is left (Server-side deadline).
Sandbox.get_info(sandbox_id) does the same without a handle. sbx.info
is the last known SandboxInfo, without calling AWS. is_running() tells
you whether the agent answers, and get_health() returns the agent's
SandboxHealth (resume_generation, kernel_state_lost, lifecycle…).
Metadata¶
metadata={"key": "value"} tags the sandbox when you create it. It is
immutable, travels with the launch and is not secret: anyone who can
mint a proxy token for the sandbox reads it. It is useful for filtering
the listing.
List¶
Sandbox.list() walks list-microvms lazily and returns
SandboxListItem (sandbox_id, state, template, template_version,
started_at, metadata). In synchronous Python it is an iterator; in
AsyncSandbox.list() a coroutine that returns the list, and in TypeScript
an AsyncIterable. Filters: template=, template_version=, states=,
metadata=, started_after= and order= (TypeScript: templateVersion,
startedAfter). For a cursor you can store and resume,
Sandbox.paginate()
(Metrics and listing).
list(metadata=) is O(n)
Without an index, filtering by metadata probes every RUNNING sandbox
(≈ 0.5-1 s per sandbox) and never sees the suspended ones. For large
fleets or paused sandboxes, the
metadata index
(optional, with a cost) solves it with a DynamoDB query.
create() options¶
| Python | TypeScript | Default | What it does |
|---|---|---|---|
template (1st argument) |
template |
RAYITO_TEMPLATE |
image name or ARN |
template_version |
templateVersion |
the latest active | specific image version |
timeout |
timeoutMs |
3600 s / 3,600,000 ms | maximum lifetime (running + suspended), cap 28,800 s; with max_lifetime or on_timeout it is the server-side deadline |
max_lifetime, on_timeout |
maxLifetimeMs, onTimeout |
— | server-side deadline |
idle |
idle |
IdlePolicy(max_idle_seconds=300) |
auto-suspend on inactivity; None / null turns it off, and with no idle it turns itself off if it does not fit before the timeout (Pause and resume) |
envs |
envs |
— | environment variables for all processes (not secret: they travel in the launch) |
metadata |
metadata |
— | immutable, non-secret tags |
cpu_time_limit |
cpuTimeLimit |
— | CPU seconds per process (RLIMIT_CPU) |
execution_role_arn |
executionRoleArn |
— | IAM role inside the sandbox (IMDSv2); without it there are no credentials inside |
logging |
logging |
"disabled" |
"cloudwatch" sends the runtime logs (needs execution_role_arn) |
allowed_ports |
allowedPorts |
only 8080 | extra ports on the proxy's main token (Ports and host) |
ingress, egress |
ingress, egress |
managed connectors | Lambda MicroVMs network connectors |
network, allow_internet_access |
network, allowInternetAccess |
unrestricted | Outbound network (only rayito-base-caps) |
transfer |
transfer |
RAYITO_TRANSFER_BUCKET |
bucket for large files and URLs (Files and S3) |
persist, persist_timeout |
persist, persistTimeoutMs |
—, 600 s | HOME on S3 that survives kill() and the 8 h mark (Persistence) |
pool |
pool |
— | take from a pool |
access_token |
accessToken |
32 random bytes | sets the sandbox secret |
region, session |
region |
the session's | AWS region and credentials |
ready_timeout, request_timeout, reconnect_timeout |
readyTimeoutMs, requestTimeoutMs, reconnectTimeoutMs |
90 / 60 / 60 s | client deadlines |
keep_on_failure |
keepOnFailure |
False |
does not terminate the MicroVM if startup fails (for debugging) |
logger |
logger |
rayito logger / silent |
where the SDK logs (only ids, states and durations) |
control_plane, transport |
controlPlane, transport |
real AWS | injectable adapters (tests, local environment) |
| — | signal |
— | AbortSignal that cancels creation |
secrets, secret_cache |
secrets, secretCache |
off | Secrets (optional, with a cost) |
index |
index |
off | Metadata index (optional, with a cost) |
tracer_provider |
tracerProvider |
off | OpenTelemetry |
size |
size |
the usual image | Sizes (optional) |
mounts |
mounts |
off | S3 mounts (optional, with a cost) |
volumes |
volumes |
off | EFS volumes (experimental, optional, with a cost) |
events |
events |
off | Events and webhooks (optional, with a cost) |
telemetry |
telemetry |
off | OTLP export (optional, with a cost) |
gateways |
gateways |
off | Secrets gateway (optional, with a cost) |
domain |
domain |
— | reserved for the custom domain (experimental): today it raises UnimplementedError |
All options with a cost are off by default: see Optional features.
Errors and troubleshooting¶
| Python | TypeScript | When | What to do |
|---|---|---|---|
InvalidArgumentException |
InvalidArgumentError |
no image (RAYITO_TEMPLATE), envs + metadata above the payload limit |
fix the argument; AWS was not called |
SandboxLifetimeException |
SandboxLifetimeError |
timeout or max_lifetime above 28,800 s |
lower the deadline; for more, Persistence |
SandboxNotFoundException |
SandboxNotFoundError |
connect() or get_info() of an id that does not exist or has already ended |
create a new one |
AuthenticationException |
AuthenticationError |
wrong access token or expired AWS credentials | check the stored token; aws sso login |
SandboxNotReadyException |
SandboxNotReadyError |
the agent was not ready within ready_timeout |
rayito doctor; try keep_on_failure=True and look at the logs |
QuotaExceededException |
QuotaExceededError |
the region's memory or MicroVM quota is exhausted | kill sandboxes or request a quota increase |
CapacityException |
CapacityError |
AWS has no capacity for the moment in the region | retry with backoff |
UnimplementedError |
UnimplementedError |
the image is too old for what was requested | publish an image from the current release |
Full table: Errors.
Differences from E2B¶
- The maximum lifetime is 8 h, counting suspended time. E2B keeps paused sandboxes without a limit.
connect()and the class forms need the sandbox's access token: there is no API key that replaces it.- There is no per-sandbox
cpu_count/memory_mb: the image fixes the size (size=chooses among already-published images of each size).