How Do I Give Every Student in a Class Their Own Cloud Sandbox?
Updated October 27, 2026 .md
Short answer: Build the lab once in a sandbox and save it as a named snapshot. Before class, create one small --no-env sandbox per student from it, with a TTL and an idle timeout, and send each student their own private code-server URL or SSH access. Delete them all at term end. A 30-student, 2-hour lab on small costs $1.08.
This page is for instructors running a course, a bootcamp or a one-day workshop. Students don't need a Sandboxer account, a card or anything installed: you run the sandboxes, and each student gets a link. Everything in the end-to-end section ran on prod on 2026-10-27. For a weekend event with teams, see Sandboxes for hackathons; for a vendor workshop or runnable docs, see Sandboxes for DevRel.
The decisions, and what to use for each#
| Decision | Short answer | Sandboxer feature | Worked guide |
|---|---|---|---|
| One lab image for everyone | Install the tools and the starter files once, then save the result under a versioned name | sbx snapshot <id> lab01-v1, sbx new --from lab01-v1 |
Turn a sandbox into a reusable template |
| How students get in | A private hosted code-server or JupyterLab URL for browser-only students; SSH with their own key for students with a terminal | sbx host <id> 8080, POST /sandboxes/{id}/sshkey |
code-server, JupyterLab, SSH from VS Code |
| Keeping your account out of student hands | Create every student sandbox with --no-env: none of your secrets inside, and its built-in token can only read that sandbox and host its ports |
sbx new --no-env |
Environment variables inside a sandbox |
| Bounding the cost per student | Small size, a TTL as long as the session, an idle timeout so breaks cost nothing | --type small --ttl 7200 --idle 15 |
Idle auto-pause and wake on use, Cap what sandboxes can cost |
| Starting 30 at once | Your plan's starts-per-minute limit sets the pace; start early | sbx limits --json |
Rate limits and concurrency caps |
| End of term | Copy out what you grade, then delete every sandbox and the snapshot | sbx scp, sbx delete <id> --yes, sbx snapshot rm |
Clean up orphaned sandboxes |
You run the sandboxes, or students join an organization?#
There are two ways to set up a class. For most classes, you run the sandboxes.
| You create every sandbox (recommended) | Students are members of your organization | |
|---|---|---|
| Student needs | A link (or an SSH key) | A Sandboxer sign-in |
| Who pays | Your wallet | The organization's wallet, per seat: plan price × seats (Organizations) |
| Can students see each other's sandboxes? | No. Each student has only their own URL or key | Yes. Every member sees the organization's sandboxes, and --no-env ones are usable by every member from the start |
| Can they start from your lab snapshot? | You do it for them | Not by name: named snapshots are visible only to their creator. You'd give them a share code (sbx snapshot share) |
| Per-student cap | Size × TTL bounds each sandbox; there is no per-student dollar setting | An owner can cap a member's machine time with PATCH /orgs/{orgId}/members/{userId} and usageCapSeconds, API only. Over the cap, new starts get 402 team_member_cap_reached; running sandboxes keep running |
An organization fits the staff, not the students. Put yourself and your teaching assistants in one: every member can see the class sandboxes your organization pays for, and can open and exec into the --no-env ones to help a stuck student. Use the owner-only member caps for the TAs who start their own sandboxes. We didn't create an organization or set a cap for this guide; that column is from the organization docs and the API code.
Getting students in#
| Route | Student needs | What we measured on prod |
|---|---|---|
| Private code-server URL | A browser | 401 without the token. The token URL sets a cookie and redirects, then the editor page loads with 200. One student's token on another student's URL got 401 |
| Private JupyterLab URL | A browser | Not run here; the JupyterLab guide runs the same pattern on port 8888 |
| SSH with the student's own key | An SSH client (or VS Code Remote-SSH) | The student's public key is authorized through the API, then plain ssh to the sandbox's IPv4 sshEndpoint connects in 1.78 s. The same key against the other student's sandbox got Permission denied (publickey) |
sbx ssh with a per-student API key |
The sbx CLI |
Not run. API keys can be limited to one sandbox, a list of actions and an expiry, for example --sandbox <id> --actions sandbox.read,ssh --ttl 120d. Never give students your own key |
sbx handoff isn't a student-access tool. It moves your own Claude Code or Codex session and repo from your laptop into a sandbox (MCP docs).
One lab, two students, end to end#
We built a lab image with code-server and a Python exercise, saved it, started two student sandboxes from it, checked each one and deleted everything. Sizes were all small. Private URLs and tokens are replaced with <token>.
1. Build the lab image#
sbx new --name classroom-1 --type small --no-env --ttl 3600
id=sb_11ub6acn7l
sbx exec $id --timeout 300 "curl -fsSL https://code-server.dev/install.sh | sh >/tmp/cs.log 2>&1; code-server --version"4.143.1 22ddf60dbc52f164cff13620ab515086dd08c90a with Code 1.143.1The code-server install script took 6.8 s. Then the lab itself: a starter file with a TODO, the tests that grade it, a venv with pinned pytest, and code-server as a systemd service that starts on every boot. We piped this script in with sbx ssh $id -- bash -s < lab.sh:
set -e
mkdir -p ~/lab01 && cd ~/lab01
cat > stats.py <<'P'
def mean(xs):
"""Return the arithmetic mean of xs. TODO: handle an empty list."""
raise NotImplementedError
P
cat > test_stats.py <<'P'
import pytest
from stats import mean
def test_mean():
assert mean([1, 2, 3, 4]) == 2.5
def test_empty():
with pytest.raises(ValueError):
mean([])
P
uv venv -q --seed ~/lab01/.venv --python 3.12
VIRTUAL_ENV=~/lab01/.venv uv pip install -q pytest==8.3.3
echo 'source ~/lab01/.venv/bin/activate' >> ~/.bashrc
cat > ~/.config/code-server/config.yaml <<C
bind-addr: 0.0.0.0:8080
auth: none
cert: false
C
sudo systemctl enable --now code-server@user
sleep 3
systemctl is-active code-server@user
curl -s -o /dev/null -w "local %{http_code}\n" localhost:8080/
cd ~/lab01 && ~/lab01/.venv/bin/python -m pytest -q -p no:cacheprovider --tb=no | tail -1active
local 200
2 failed in 0.01sBoth tests fail before any student touches the code, so the grader works. auth: none is safe only because the hosted URL's token guards the editor; never host this port with --public.
2. Save it and delete the builder#
sbx --json snapshot $id classroom-lab01-v1
sbx delete $id --yes{"ok":true,"type":"snapshot.named.info","snapshot":{"name":"classroom-lab01-v1","snapshotId":"69994b2c-…","sourceSandboxId":"sb_11ub6acn7l","status":"ready","type":"small",…}}The builder lived about 40 seconds, create to delete.
3. Start a student's sandbox and hand it over#
These are the commands we ran for each student. For a class, run them in a loop over your roster and write each student's id and URL to a file you keep private.
sbx --json new --from classroom-lab01-v1 --name classroom-2 --type small --no-env --ttl 7200 --idle 15
sbx host sb_4ac1yp0yd9 8080 --title "Lab 1"{"id":"sb_4ac1yp0yd9","name":"classroom-2","type":"small","noEnv":true,"ttlSeconds":7200,"idleStopMinutes":15,"sshEndpoint":"62.238.91.250:22021","subdomain":"exact-epic-salmon"}https://exact-epic-salmon-8080.box.sandboxer.com?_token=<token>That URL is what the student gets. Checking it from outside:
curl -s -o /dev/null -w '%{http_code}\n' https://exact-epic-salmon-8080.box.sandboxer.com/
curl -s -c jar -o /dev/null -w '%{http_code} %{redirect_url}\n' "https://exact-epic-salmon-8080.box.sandboxer.com?_token=<token>"
curl -s -b jar -w '%{http_code}\n' -o page.html https://exact-epic-salmon-8080.box.sandboxer.com/401
302 https://exact-epic-salmon-8080.box.sandboxer.com/
200The page that came back was code-server's workbench. The service started by itself in the new sandbox, because the snapshot holds the enabled systemd unit, and the lab files and venv were there:
sbx exec sb_4ac1yp0yd9 "systemctl is-active code-server@user; ls ~/lab01; cd ~/lab01 && .venv/bin/python -m pytest -q -p no:cacheprovider --tb=no | tail -1"active
__pycache__
README.md
stats.py
test_stats.py
2 failed in 0.00sFor a student who wants a terminal instead, authorize their public key (never a private key) and give them the sshEndpoint:
curl -sS -X POST https://sandboxer.com/api/v1/sandboxes/sb_4ac1yp0yd9/sshkey \
-H "Authorization: Bearer $SANDBOXER_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"key\":\"$(cat student1_key.pub)\"}"
# the student runs:
ssh -i student1_key -p 22021 user@62.238.91.250 'hostname; cd lab01 && .venv/bin/python -m pytest -q -p no:cacheprovider --tb=no | tail -1'exact-epic-salmon
2 failed in 0.00s4. The second student#
The second sandbox, sb_15e0iaw16a, came from the same snapshot and checked out the same way: its own subdomain (tiny-cyan-sierra), 401 without its token, 200 with it, code-server active, the same two failing tests. Two isolation checks:
- The first student's token on the second student's URL:
401. - The first student's SSH key against the second student's sandbox:
Permission denied (publickey). Each sandbox also had its own SSH host key.
We ran the two students one after another, not side by side, to keep our test small. In class they run side by side.
5. Clean up#
sbx delete sb_4ac1yp0yd9 --yes
sbx delete sb_15e0iaw16a --yes
sbx snapshot rm classroom-lab01-v1
sbx --json info sb_4ac1yp0yd9{"event":"error","error":"Sandbox not found","code":"not_found","status":404,…}After the delete, the first student's old private URL answered 404. All three sandbox ids returned 404, and sbx list showed no classroom sandboxes.
Timings#
Single runs, timed from our client, so they include CLI and network time:
| Step | Time |
|---|---|
Create the builder (small) |
3.20 s to ready |
| Install code-server | 6.8 s |
| Lab files, venv, pytest, service | 8.6 s (3 s of it a deliberate wait) |
| Save the named snapshot | 2.13 s |
| Student sandbox from the snapshot | 3.20 s to ready, both times |
sbx host |
0.30 s |
Editor page through the private URL, after host |
0.67 s and 0.72 s, first try |
| SSH with the student's own key | 1.78 s |
| Delete a sandbox | 2.13 s |
What a 30-student, 2-hour lab costs#
From pricing: small (2 vCPU, 4 GB) costs $0.018/hour and default (4 vCPU, 8 GB) $0.036/hour, billed per second while a sandbox runs.
| Scenario | Arithmetic | Cost |
|---|---|---|
30 students, 2 hours, small, all running the whole time |
30 × 2 h × $0.018 | $1.08 |
Same on default |
30 × 2 h × $0.036 | $2.16 |
12 weekly labs of that size in a term, small |
12 × $1.08 | $12.96 |
| Building the image | About 40 s of small |
Under $0.001 |
--ttl 7200 makes each run stop itself 2 hours after it starts. In the API code, a wake from idle pause sets a new deadline 2 hours from the wake, so a sandbox a student opens again after class runs again. To make $1.08 a real ceiling, delete the sandboxes when the session ends, or use --idle 0 so they don't wake. Idle pause lowers the bill during class, because a paused sandbox bills no compute. The $20 plan includes $20 of machine time a month, which covers the 12-lab term. Check each student's real number with sbx usage <id>: two seconds into its life, our second student's sandbox showed "seconds":2,"dollars":0.00002.
Limits that matter for a class#
| Limit | Free trial | $20 plan | $100 plan |
|---|---|---|---|
| Sandboxes running at once | 2 | 100 | 300 |
| Starts per minute / hour / day | 12 / 60 / 200 | 30 / 210 / 840 | |
| Longest TTL | 2 hours | 30 days | 30 days |
- The trial can't run a class. It allows 2 sandboxes at once.
- Start early. On the $20 plan, 30 creates spread over three minutes (12, 12, then 6). Creates, resumes and forks each count as one start, and in the API code a wake from idle pause is recorded as a start too. If all 30 sandboxes pause over a break, the 30 wakes after it are another 30 starts, which reaches the 60-an-hour limit. For a 2-hour session, either create the sandboxes more than an hour ahead, set
--idlelonger than your breaks, or use--idle 0and let the TTL stop them. - Storage. Stopped sandboxes and named snapshots count toward your plan's included storage (50 GB on $20). At the limit, new sandboxes get
402 storage_limit_reached. Delete last term's sandboxes and old lab versions.
Honest limits#
- No per-student dollar cap when you run the sandboxes. Size × TTL bounds each one; the member usage cap applies only to organization members starting their own sandboxes, and only through the API.
- The private URL is a password. Anyone a student forwards it to gets in. To revoke one, the documented path is switching the port to
--publicand back to--private, which mints a new token but leaves it open for that moment; replacing the sandbox is the safer fix. - Students have root in their VM. That's the point of a VM per student, and the isolation is between VMs. Inside their own sandbox they can install anything and reach the internet.
- SSH endpoint ports get reused. Our second student's sandbox got the same IPv4 port as the first, deleted one, with a different host key. A student whose old sandbox was replaced sees a host-key warning until they remove the old
known_hostsline. - Where it runs. The sandbox we checked reported
region: "eu-central". Sandboxer has two regions, us-east (Ashburn, Virginia) and eu-central (Helsinki, Finland); see regions. Students far from both will feel the round trip in a terminal more than in a browser editor. - No GPU. Sizes are CPU-only, up to
xlarge(16 vCPU, 32 GB, $100 plan or higher). A machine-learning class can teach on small models, not train large ones. - Moving to a smaller size isn't supported yet. Pick the size in the snapshot you build; a new, smaller sandbox means copying the student's work over.
FAQ#
Do students need a Sandboxer account?#
No, not when you run the sandboxes. A student needs only their private URL, or an SSH client and the public key you authorized. They need an account only if they join your organization or start their own copy from a share code.
What happens to a student's work if their sandbox stops?#
Nothing is lost. Auto-stop and idle pause keep the disk and memory, and an idle-paused sandbox wakes on the next request to its private URL. A sandbox stopped by its TTL stays stopped until you run sbx resume <id>. Copy work you grade out with sbx scp <id>:/home/user/lab01 . before you delete.
How do I update the lab mid-term?#
Change a sandbox built from the current image, save it under a new name such as lab01-v2, and create new students' sandboxes from that. Sandboxes already running keep the version they started from; reusing a name replaces the snapshot.
Can teaching assistants get into a student's sandbox?#
Yes, if they are members of the organization that pays for the sandboxes: members can open and exec into --no-env organization sandboxes. Otherwise you exec into it yourself with sbx exec or sbx ssh.
Related#
More guides
For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.
For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.
The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.
If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.