Migrate from Beam Sandboxes to Sandboxer
Updated October 27, 2026 .mdSandboxer vs Beam
Short answer: Port Beam Sandbox code to Sandboxer's Python SDK. Create, exec, file and port calls map closely. Images become setup scripts or named snapshots, memory snapshots become stop and resume, and expose_port becomes a hosted HTTPS port. Copy files with sbx scp. Keep GPU work on Beam, because Sandboxer has no GPUs.
This page covers Beam Sandboxes: Sandbox(...).create(), sb.process, sb.fs, expose_port and snapshots. It doesn't cover Beam's VMs, endpoints or GPU tasks. If your sandboxes need a GPU, stay on Beam: Sandboxer has no GPU sizes (Machines).
Concept map#
Facts about Beam come from its sandbox overview and the pages linked below, checked on 2026-10-27.
| Beam | Sandboxer |
|---|---|
| Sandbox: "a container you control through process, filesystem, networking, and snapshot APIs" (overview) | Sandbox: a Cloud Hypervisor microVM with its own kernel, systemd and sudo (Machines) |
cpu and memory per sandbox; default 1 CPU and 128 MiB |
Four fixed sizes: small (2 vCPU, 4 GB), default, large, xlarge |
Image(python_version=...).add_python_packages([...]) |
Ubuntu 24.04 base, plus a setup script or a named snapshot (from_=) |
keep_warm_seconds: default 600 s, -1 until you terminate it |
ttl_seconds: default 1 hour, up to 30 days, or none on a paid plan (Long-running) |
sb.terminate() ends the sandbox |
sb.delete(), or sb.stop() to keep it for later |
create_image_from_filesystem() returns an image ID for Image.from_id |
sb.snapshot("name"), then client.create(from_="name") (Snapshots) |
snapshot_memory() returns a checkpoint ID for create_from_memory_snapshot, "on supported runtimes" |
Every stop saves memory; sb.resume() brings back the same sandbox, with memory on a machine with the same CPU type |
| Several sandboxes from one memory checkpoint, each with its own identity | sb.fork(): a copy of the disk, booted fresh with a new identity (no memory) |
expose_port(8000) or ports=[...], public URLs |
sb.host(8000): https://<slug>-8000.box.sandboxer.com, private with a token by default (Hosting) |
env={...}, and brokered secrets=[...] |
env={...} per sandbox, or an environment (sbx env) for every sandbox (Environments) |
docker_enabled=True plus Image(...).with_docker() |
Docker with BuildKit and Compose in every sandbox |
gpu, gpu_count |
No GPUs |
Volume, CloudBucket, DurableDisk |
The sandbox's own disk, kept across stop and resume |
Sandbox().connect(container_id) |
client.get(sandbox_id) |
Python and TypeScript SDKs, beam CLI |
Python and TypeScript SDKs (sandboxer-sdk), the sbx CLI and the REST API |
API and SDK mapping#
Beam's calls are from its processes, filesystem, networking and snapshots pages. Ours are from sandboxer-sdk 0.1.1 (Python SDK), which we ran on prod for this page.
| Beam (Python) | Sandboxer (Python) | sbx CLI |
|---|---|---|
Sandbox(cpu=1, memory="1Gi", keep_warm_seconds=600).create() |
client.create(type="small", ttl_seconds=600) (waits until ready) |
sbx new --type small --ttl 600 |
Sandbox(image=Image.from_id(image_id)).create() |
client.create(from_="name") |
sbx new --from name |
sb.process.run_code("print(6 * 7)") |
sb.exec(["python3", "-c", "print(6 * 7)"]) |
sbx exec <id> -- python3 -c "print(6 * 7)" |
p = sb.process.exec("ls", "-la"), p.wait(), p.stdout.read() |
run = sb.exec("ls -la"), run.stdout, run.exit_code |
sbx exec <id> "ls -la" |
sb.process.exec(...) left running (a server) |
sb.start("..."), then attach(), status(), kill() |
sbx exec <id> --detach "..." |
for line in p.logs: |
for frame in sb.exec_stream(...): |
sbx exec streams by default |
sb.fs.write_text(path, text), sb.fs.read_text(path) |
sb.files.write(path, text), sb.files.read(path) |
|
sb.fs.upload_file("input.csv", "/workspace/input.csv") |
sb.files.write("workspace/input.csv", open("input.csv", "rb").read()) |
sbx scp ./input.csv <id>:/home/user/workspace/ |
sb.fs.download_file(remote, local) |
sb.files.download(path) (up to 50 MiB) |
sbx scp <id>:/home/user/workspace/out.csv . |
sb.expose_port(8000), sb.list_urls() |
sb.host(8000, public=True).url |
sbx host <id> 8000 --public |
sb.update_ttl(600) |
sb.extend(600) |
sbx extend <id> --ttl 600 |
sb.snapshot_memory(), create_from_memory_snapshot(id) |
sb.stop(), sb.resume() |
sbx stop <id>, sbx resume <id> |
sb.create_image_from_filesystem() |
sb.snapshot("name") |
sbx snapshot <id> name |
sb.terminate() |
sb.delete() |
sbx delete <id> --yes |
Two habits change. Beam's process.exec takes the command as separate arguments and returns a handle; our exec takes a shell string or one list and returns the finished result, and start is the call for a process that keeps running. And Beam ends a sandbox when keep_warm_seconds runs out, while ours stops it with memory and disk kept, so you can resume it.
Before and after#
From Beam's docs: https://docs.beam.cloud/v2/sandbox/overview ("Start a web server", checked on 2026-10-27):
from beam import Image, Sandbox
sb = Sandbox(image=Image(python_version="python3.11"), keep_warm_seconds=300).create()
sb.fs.write_text("/workspace/index.html", "Hello from a sandbox")
process = sb.process.exec(
"python3", "-m", "http.server", "8000", "--bind", "0.0.0.0",
cwd="/workspace",
)
print(sb.expose_port(8000))
print(sb.container_id)And the upload call, from https://docs.beam.cloud/v2/sandbox/filesystem ("Transfer local files"):
sb.fs.upload_file("input.csv", "/workspace/input.csv")The same script on Sandboxer. Install the SDK with pip install sandboxer-sdk and set SANDBOXER_API_KEY. Sandboxer() reads the key from the environment. Python 3 is already in the Ubuntu image, so there's no image to declare:
import time, urllib.request
from sandboxer import Sandboxer
client = Sandboxer()
sb = client.create(type="small", ttl_seconds=600, name="aeo-migrate-beam-2")
sb.files.write("workspace/index.html", "Hello from a sandbox")
sb.files.write("workspace/input.csv", open("input.csv", "rb").read())
sb.start("python3 -m http.server 8000 --bind 0.0.0.0", cwd="workspace")
url = sb.host(8000, public=True).url
print(url)
time.sleep(2) # a new route can answer "Port not hosted" for about a second
print(urllib.request.urlopen(url, timeout=10).read().decode())
print(sb.id)We ran it on prod on 2026-10-27. The whole script, including creating the VM, took 5.5 seconds in that run:
https://hazel-vivid-ocelot-8000.box.sandboxer.com
Hello from a sandbox
sb_2rz1cy7snwNotes from the run:
- Paths in
files.writeare relative to/home/user, and missing directories are created.workspace/index.htmllanded in/home/user/workspace/. - Our first try called the URL right after
host()returned and got a404page titled "Port not hosted". A retry 0.9 seconds later got200. Wait a moment, or retry, before the first request. host()makes the URL private with a_tokenunless you passpublic=True. Beam's preview URLs are always public.
Stop, resume, fork and a template#
Beam's quickstart snapshots memory, terminates the sandbox and starts copies from the checkpoint. Here the same sandbox stops and comes back, and copies come from its disk. We ran this on prod against the sandbox above:
import sys, time, urllib.request
from sandboxer import Sandboxer
client = Sandboxer()
sb = client.get(sys.argv[1])
url = sb.host(8000, public=True).url
print("pid before:", sb.exec("pgrep -f 'http.server 8000'").stdout, end="")
t = time.time(); sb.stop(); print("stopped", sb.state, f"{time.time()-t:.1f}s")
t = time.time(); sb.resume(); print("resumed", sb.state, f"{time.time()-t:.1f}s")
print("pid after:", sb.exec("pgrep -f 'http.server 8000'").stdout, end="")
print("GET after resume:", urllib.request.urlopen(url, timeout=30).read().decode())
fork = sb.fork(type="small", ttl_seconds=600)
print("fork", fork.id, fork.exec("cat workspace/input.csv | wc -l").stdout, end="")
fork.delete()
snap = sb.snapshot("aeo-migrate-beam-tpl")
print("named snapshot", snap.name, snap.status)pid before: 549
stopped archived 0.6s
resumed ready 0.6s
pid after: 549
GET after resume: Hello from a sandbox
fork sb_x1vzt29zs8 3
named snapshot aeo-migrate-beam-tpl readyWhat this shows:
- The web server kept the same PID (549) across
stop()andresume(), and the same URL answered right away. After the resume the sandbox showedmemoryRestored: true. Memory comes back only when the resume lands on a machine with the same CPU type. Otherwise the sandbox cold-boots from its disk: files are kept, running processes are not, and it showsmemoryRestored: falsewithresumeReason: "cpu_mismatch"(Snapshots). - The fork had the uploaded CSV (3 lines). A fork copies the disk only, never memory or running processes, and boots with a new id and subdomain (Snapshots). Start your server again in the fork, or
systemctl enableit so it starts on boot. If you need several copies with processes already running, as Beam's memory checkpoints give you, that has no direct counterpart here. - The named snapshot is the counterpart of
create_image_from_filesystem(). Start new sandboxes from it withclient.create(from_="aeo-migrate-beam-tpl")orsbx new --from aeo-migrate-beam-tpl. It's kept until you delete it (sbx snapshot rm <name>).
Move your data and environment#
Files. A Beam sandbox can't move over. Copy out what you need with sb.fs.download_file(...) (Beam docs), or read it from your Volume. Then send it to the new sandbox with sbx scp. We ran these on prod against sb_2rz1cy7snw:
tar czf proj.tgz proj
sbx scp ./proj.tgz sb_2rz1cy7snw:/home/user/ # exit 0
sbx exec sb_2rz1cy7snw "tar xzf proj.tgz && python3 proj/main.py" # hi from my beam project
sbx exec sb_2rz1cy7snw --cwd /home/user/workspace -- wc -l input.csv # 3 input.csv
sbx scp sb_2rz1cy7snw:/home/user/workspace/input.csv ./back.csv # and back outFor a directory, use sbx scp -r. The SDK's files.write and files.read take files up to 5 MiB, and files.download up to 50 MiB.
Volumes and durable disks. Our docs don't describe a volume shared by many sandboxes or a mounted bucket. A sandbox's disk is kept across stop and resume, so data you put there stays. To give many sandboxes the same data, put it in a named snapshot and start them from_= it, or download it in a setup script.
Images. Turn your Beam Image into a shell script. add_python_packages(["requests"]) becomes pip install --user --break-system-packages requests, and system packages become sudo apt-get install -y .... Pass the script as setup_script= (up to 64 KB). It runs in the background once the sandbox is ready; pass wait_for_setup=True to have create return after it finishes (Long-running). If installs are slow, run the script once, save the result with sb.snapshot("web-stack"), and start later sandboxes with client.create(from_="web-stack"). Drop with_docker() and docker_enabled=True: Docker is already installed.
Snapshots and checkpoints. Beam image IDs and checkpoint IDs don't carry across. Rebuild a template from a setup script as above. Then pick the counterpart by what you used the Beam snapshot for:
| You used Beam's | To | Use on Sandboxer |
|---|---|---|
| Memory snapshot | Pause one sandbox and come back to it | sb.stop() and sb.resume(): memory kept on the same CPU type, otherwise a cold boot with files kept |
| Memory snapshot | Start many copies with processes running | sb.fork() (disk only), then start the processes |
| Filesystem snapshot | A reusable base image | sb.snapshot("name"), then from_="name" |
Secrets. Beam's brokered secrets=[...] hand the guest a placeholder and substitute the real value in HTTPS headers to approved hosts (Beam docs). We don't have that: values you pass are visible inside the sandbox. Use env={...} on create for one sandbox, or an environment for values every sandbox should get: sbx env set-var base KEY=value or POST /secrets (Environments).
Ports. Replace expose_port(port) and ports=[...] with sb.host(port) or sbx host <id> <port>. The URL is private with a _token by default; pass public=True (CLI --public) to open it. The name stays the same across stop and resume. If your server listens on 127.0.0.1 only, use host(port, localhost=True); Beam requires 0.0.0.0.
Lifetimes. Beam's default is 600 seconds and the sandbox ends. Ours is 1 hour, and the sandbox stops instead of ending. Set ttl_seconds on create, call sb.extend(ttl_seconds) in place of update_ttl, or pass ttl_seconds=None on a paid plan to turn auto-stop off. During the trial the most is 8 hours. Synchronous exec calls time out after at most 600 seconds; use sb.start(...) for longer jobs (Long-running).
What's different#
What you gain:
- Memory survives a stop of the same sandbox. In our run the server kept its PID and URL through
stop()andresume(). Memory comes back when the resume lands on a machine with the same CPU type; otherwise the sandbox boots from disk with files kept. On Beam, a memory snapshot is restored into a new sandbox with its own identity and URLs (Beam docs). - A full machine. Every sandbox is a VM with systemd, Docker and
/dev/kvm. On oursmallsandbox,docker --versionprintedDocker version 29.9.0and/dev/kvmwas present, with no image changes. - Longer lifetimes and idle pause. A paid-plan sandbox can run with no time limit.
sbx new --idle 60pauses it with memory after an hour without activity and wakes it on the next use (Long-running). - A desktop. Every sandbox has a streamed desktop with Chrome (Desktop).
What you lose or must change:
- GPUs. Beam sandboxes can request a GPU (
gpu="RTX4090"). Sandboxer has none: on our sandbox,nvidia-smiwas not found and there was no/dev/nvidia*. Keep GPU work on Beam. - Sizing. Beam takes any
cpuandmemory. We sell four fixed sizes. You can grow a sandbox on resume (sbx resume <id> --type large), and a size change cold-boots from disk. Moving to a smaller size isn't supported yet: to go smaller, create a new sandbox of that size and copy your files. - Outbound network rules. Beam has
block_network, allowlists of up to 64 hostnames, IPs or CIDRs, and runtime changes (Beam docs). Our docs describe fixed blocks (other sandboxes, internal networks, SMTP port 25), not per-sandbox allowlists (Security). - Brokered secrets. See above: there's no placeholder substitution here.
- Start volume. Our plans limit starts: 12 a minute, 60 an hour and 200 a day on the $20 plan, up to 2,400 a day on the $2,000 plan (Billing). Creates, resumes and forks each count. Check with
sbx limitsbefore a burst. - Process API. Beam's
execacceptsstdinand returns a handle youwait()on. Ours runs to completion (exec) or in the background (start, withattach,statusandkill).
Pricing#
Beam's sandbox rates come from its pricing page, checked on 2026-10-27: $0.0000375 per physical core per second (one core is 2 vCPU) and $0.0000064 per GiB of memory per second, billed only while the sandbox runs. Ours come from /pricing.
| Machine | Beam Sandboxes | Sandboxer |
|---|---|---|
| 4 vCPU, 8 GB, one hour | 2 cores and 8 GiB: about $0.454 per hour | default: $0.036/hour |
| 2 vCPU, 4 GB, one hour | 1 core and 4 GiB: about $0.227 per hour | small: $0.018/hour |
| After the work is done | The sandbox ends; there's no stopped state to pay for | A stopped sandbox isn't billed; it counts toward included storage (50 GB on the $20 plan) |
Both bill per second or finer while a sandbox runs. One 4 vCPU, 8 GB sandbox running 8 hours a day for 22 days is 176 hours: about $79.96 on Beam and $6.34 on Sandboxer. Beam's Developer plan is $20 a month with $20 of credits, and it lists no egress fees. Its GPU and VM prices have no counterpart in this table. Sandboxer plans start at $20 a month, paid back as machine time, with a 7-day trial of 25 hours.
FAQ#
Can I keep using Beam for GPU jobs and move only sandboxes?#
Yes. That's the split we suggest. Sandboxer has no GPUs, so run GPU sandboxes and endpoints on Beam and move CPU sandboxes, like agent workspaces and code execution, to Sandboxer.
What replaces snapshot_memory()?#
To pause and continue one sandbox, sb.stop() and sb.resume(): memory and processes come back on a machine with the same CPU type, and otherwise it cold-boots with files kept. To make copies, sb.fork(), which copies the disk only. For a reusable base, sb.snapshot("name").
Do I need to declare an image?#
No. Every sandbox starts from Ubuntu 24.04 with Python 3, Docker and common tools. Put extra installs in setup_script=, or save a prepared sandbox as a named snapshot and start from it.
Beam also has VMs. Is this page about those?#
No. This page covers Beam Sandboxes. Beam's docs describe its VMs as persistent Linux machines without GPUs that can pause with memory. Their calls (beam vm ...) are not mapped here.
Can I make a sandbox smaller later?#
Not the same sandbox. Growing works on resume (--type large), but moving to a smaller size isn't supported yet. Create a new sandbox of that size and copy your files, or start small and grow when you need to.