The Best Code Execution Sandboxes for Education Platforms in 2026
If you're building a coding course, an interactive tutorial, an autograder, an online judge, a bootcamp platform, or the CS department's assignment infrastructure, you will at some point have to decide where student code runs. It is a decision people usually make once, quickly, under deadline pressure, using whatever was easiest that week — and then live with for four years, because migrating an execution backend after two thousand exercises have been authored against it is genuinely miserable. This is a buyer's guide for making that decision on purpose.
I'm Ajay; I built PandaStack, which is one of the options in the microVM category below. So read this as a vendor's roundup and weight it accordingly. The way I keep it useful anyway: I'll be specific and numeric only about our own platform, describe every other category and product in qualitative terms drawn from its own documentation, and never print a competitor's latency or pricing figure. Those change monthly and are easy to mis-measure; verify anything load-bearing against each vendor's current docs and pricing page before you commit a semester to it.
Why education is not just "generic code execution"
Most sandbox marketing is written for AI agents or CI. Education overlaps with both and then diverges in ways that break naive capacity plans. Six things make this workload its own shape:
- The traffic is a step function, not a curve. Nothing happens for six days. Then a lecture starts and four hundred people click "Run" on the same exercise inside ninety seconds, or an assignment deadline arrives and the submission queue takes a semester's worth of load in the last nine minutes. Your p50 is irrelevant; your behaviour at 40× baseline is the entire product experience, and it happens at 11:59pm when nobody is awake.
- The code is untrusted, but rarely malicious — which makes it worse, not better. Every intro CS cohort contains at least one student who will discover `while True: os.fork()` on their own, and they will do it at 11:52pm the night an assignment is due. They are not attacking you. They are learning what a process is. The blast radius is identical either way, and a small minority genuinely will go looking for the answer key, other students' submissions, or the grader's environment variables.
- Cost sensitivity is per seat, and the seats are cheap. An edtech platform's revenue per student per month is often smaller than one hour of a modest cloud VM. Any design that keeps a container or a VM warm per active learner falls apart at the second decimal place. What you need is idle cost near zero and a marginal cost per execution measured in fractions of a cent.
- Latency expectations come from a REPL, not a build system. A student who presses "Run" on a five-line exercise expects the answer before they finish reading their own code. Multi-second provisioning turns a tutorial into a chore, and the pedagogical damage is real: the tight edit-run-observe loop is most of how people learn to program.
- You need many language runtimes, and the list only grows. Python and JavaScript get you started. Then the systems course wants C with gcc and valgrind, the databases course wants a real Postgres, the mobile track wants Kotlin, and someone's capstone needs Rust. A backend that makes adding a runtime a platform-team project will quietly cap your curriculum.
- Grading must be deterministic and reproducible months later. A mark is an academic record. If a student appeals in week 12, you have to reproduce the week-3 environment exactly — same interpreter patch version, same library versions, same CPU-time budget. "We upgraded numpy" is not a sentence you want to say to an appeals committee.
And then there's the part that isn't engineering. If your learners include minors, or you're selling into universities, the execution backend is inside your compliance perimeter: student submissions are education records, prompts and code can contain personal data, and "where does the code physically execute" becomes a procurement question with a form attached. Institutions will ask about data residency, retention, subprocessors, and whether student work is used for training. Have real answers before the security review, not during it — and get them from counsel who knows your jurisdiction, not from a blog post.
The evaluation checklist
Every option below will run `print("hello")` and hand you back stdout. That baseline tells you nothing. Score candidates on these, and work out which two or three are actually forcing your hand before you compare anything — the winner flips completely depending on which you weight.
- Isolation boundary. Shared kernel, user-space kernel, or hardware-virtualized VM? This decides what a determined student can reach, and it's the one property you cannot bolt on later.
- Resistance to accidental denial of service. Not escape — exhaustion. What happens to everyone else when one submission forks 30,000 processes, allocates 40 GB, or writes `/dev/zero` to a file until the disk fills? Ask specifically about process, memory, disk, and CPU caps, and about which of them are enforced outside the code being limited.
- Burst behaviour and provisioning latency. Measure cold, at your spike concurrency, in your region — not warm and single-threaded. Ask what the queue does when demand exceeds capacity: a fair queue with honest "you're 40th" feedback beats silent timeouts every time.
- Language and runtime breadth, and how you add one. Is a new language a config change, a Dockerfile, or a support ticket? Can a course author pin a specific compiler version for one assignment?
- Determinism and reproducibility. Immutable, versioned environment images; pinned toolchains; CPU-time limits rather than wall-clock where the mark depends on it. Can you re-run a submission from March in August and get the same result?
- Cost model at your shape. Per-execution, per-second of active CPU, or per-hour of provisioned capacity? What does idle cost? Model it at 10,000 dormant learners and 400 concurrent ones, not at your current traffic.
- Operational burden. Who reaps orphaned workloads at 3am, rolls the runtime images, and scales the fleet before the deadline? This is the cost that doesn't show up on the invoice.
- Data handling and residency. Where does student code run and get stored, for how long, under whose subprocessors, and can you self-host if an institution insists? Also: can you prove one student's execution never shared a machine with another's?
- Egress control. Can you default-deny outbound network access and allowlist exceptions per exercise? Without it, "my program submits my homework to a Discord bot" is a feature, and so is exfiltrating the test harness.
- Escape hatches. Can a student open an interactive terminal, run a multi-process program, use sockets, or install a package — when the curriculum legitimately requires it? Restrictions that block the systems course are a curriculum constraint disguised as a security control.
The options, honestly
Browser-only execution (WASM, Pyodide, WebContainers-style)
Compile the runtime to WebAssembly and run it in the learner's own tab. Pyodide puts CPython (and a good chunk of the scientific stack) in the browser; WebContainers-style approaches run a Node-like environment client-side; there are WASM builds of many other runtimes now. For interactive tutorials and intro exercises this is close to unbeatable, and I'd genuinely encourage you to start here if it fits.
The economics are the pitch: execution happens on hardware you don't pay for, so your marginal cost per run is zero and your capacity at a deadline spike is "however many browsers exist." The fork bomb problem largely evaporates too, because the student's own tab is the thing that hangs — a feedback loop with excellent pedagogical properties. Latency is instant after the initial runtime download, and it works offline.
The ceiling is real and you will hit it. You are limited to what someone has compiled to WASM, which rules out large parts of a CS curriculum: real processes and `fork`, threads in many runtimes, raw sockets, most native extensions, anything needing a filesystem with real semantics, anything needing a database server, and the systems-programming courses that are the whole point of teaching C. Package installation ranges from awkward to impossible. Initial payloads can be tens of megabytes on a phone connection. Determinism is weaker than it looks, because the execution environment is the student's browser and version — a submission that passes in Chrome and fails in Safari is a support ticket you cannot reproduce. And you cannot trust results for grading at all: the student controls the entire execution environment, so "the tests passed" means "the tests reported passing on a machine the student owns."
- Best fit: interactive tutorials, docs with runnable examples, intro exercises, marketing playgrounds, and anything where instant feedback matters more than fidelity.
- Wrong fit: graded submissions, anything multi-process or systems-level, anything needing a database or the network, and any course whose curriculum outgrows the WASM ecosystem.
Shared-kernel container runners
The most common answer in the wild: a container per submission or per session, on a pool of nodes, usually orchestrated by Kubernetes or a homegrown queue. It's cheap, the tooling is mature, everyone on your team already knows Docker, adding a language is a Dockerfile, and startup is fast. For a platform whose learners are enrolled, identified students in a mostly-trusted institutional setting, this is a defensible choice — and it's what most of the education platforms you've used are running.
The caveat is structural and doesn't go away with configuration: every container on a node shares one host kernel. A kernel-level bug reached from student code is a node compromise affecting every other submission on that node. That's the escape story, and it's the one people focus on. The story that actually bites you more often is exhaustion. Namespaces are an isolation mechanism; they are not by themselves a resource-limiting one. Unless you have explicitly set a pids cgroup limit, a memory limit with a sane OOM policy, a CPU quota, disk quotas, and no writable shared volume, one student's accidental fork bomb becomes a node-wide event — and "the grader fell over" ten minutes before a deadline is the outage that generates the most email you will ever receive.
None of that is unfixable. Set `pids.max`, set memory limits, drop capabilities, use a read-only root filesystem with a small tmpfs, seccomp-filter aggressively, never mount the Docker socket, run as a non-root user with a distinct UID per submission (RLIMIT_NPROC is per-UID), and put a NetworkPolicy in front of egress. That configuration is genuinely good, and it's a real amount of work that has to be maintained by someone who understands why each line is there. If you want a stronger boundary without leaving your existing orchestration, a user-space kernel like gVisor or a VM-backed runtime class such as Kata is the natural bridge.
- Best fit: institutional platforms with identified learners, teams already fluent in Kubernetes, and budgets that make per-run cost the dominant constraint.
- Wrong fit: open public platforms where anyone can submit anonymously, anything where a single node-wide failure at a deadline is unacceptable, or teams without someone to own the hardening.
MicroVM platforms (PandaStack, and Firecracker-based options generally)
One rung up the isolation ladder: each execution gets its own lightweight virtual machine with its own guest kernel, isolated by hardware virtualization, behind a minimal virtio device model. Firecracker is the most common VMM here — it's the primitive underneath several large public serverless platforms, chosen there for exactly this reason — and Cloud Hypervisor via Kata is the other mainstream route. The property that matters for education is that a fork bomb, a runaway allocation, or a kernel-level bug reached from student code lands inside a guest that holds one submission and is about to be deleted anyway. It is not everyone else's problem, because there is no shared kernel for it to be everyone else's problem through.
The historical objection was startup time: cold-booting a VM per execution puts seconds in front of every "Run" click, which is fatal for a REPL-feel tutorial and pushes you toward pooling VMs — at which point you're reusing machines across students and have given back the isolation you paid for. Snapshot-restore is what removed that objection. Instead of booting, you restore a snapshot of an already-booted, already-warm machine. Here are our numbers, which I'll state precisely because they're the ones I'm allowed to stand behind: on PandaStack every create is a snapshot restore rather than a cold boot, landing at 179ms p50 and about 203ms p99, with the restore step itself around 49ms. The only slow path is the first-ever spawn of a brand-new template, which cold-boots in roughly 3 seconds and bakes the snapshot everything after restores from.
Two other properties map unusually well onto education. First, copy-on-write forking: warm one machine with the dataset loaded and the toolchain hot, then clone it per student — same-host forks run 400–750ms, cross-host 1.2–3.5s. For a lab where thirty people work through the same notebook, that's the difference between thirty setups and one. Second, the environment is a versioned baked image, which is exactly the reproducibility primitive grading needs: pin the assignment to a template, and a regrade in August restores byte-for-byte the same machine as in March. On networking, each sandbox gets its own network namespace and TAP device — PandaStack pre-allocates 16,384 /30 subnets per agent — so default-deny egress is enforced on the host side, outside the guest, where a student's code cannot flush the rules. And if a course needs a real database, a managed Postgres instance is its own VM on the same substrate (create takes 30–90s, since it blocks until Postgres is genuinely ready).
The honest counterweight: a VM per execution costs more memory than a container per execution, and that's a real line item at scale. Self-hosting a microVM platform means owning Linux KVM hosts, an agent fleet, networking, and snapshot storage — genuine operational weight, and if you don't have an infra team or the appetite to grow one, a managed service is less work and that's a legitimate reason to choose one. PandaStack's core is Apache-2.0 and self-hostable on your own hardware, which is the answer to the university that wants student code executing inside its own perimeter; whether that's a feature or a burden depends entirely on who's on your team.
- Best fit: public platforms accepting code from anyone, graded submissions where a compromised grader is an academic-integrity incident, systems courses needing real processes and kernels, and institutions demanding on-premise execution.
- Wrong fit: a small, trusted cohort where a hardened container already satisfies your threat model, or a pure tutorial product that never leaves what the browser can do.
Managed sandbox APIs and self-hosted judges
The buy-don't-build lane, and it splits into two quite different halves. On one side are the general-purpose managed sandbox APIs — E2B, Modal, Daytona and others — which grew up serving AI agents and expose a create-a-sandbox-and-run-code primitive over HTTP. You get an SDK, no fleet to operate, and someone else on call. Their isolation models differ (several are Firecracker-based; Modal's own security docs describe gVisor; others describe VM-like isolation without naming a hypervisor), so confirm the backend in each vendor's documentation rather than from a comparison table. Their pricing is metered on some mix of CPU time, memory, creates, and storage — check current pages, and specifically model the deadline spike, because burst concurrency limits and per-invocation minimums are where education workloads surprise people.
On the other side are the purpose-built judges: Judge0 is the reference point, along with the older ioi/isolate-style sandboxes that competitive-programming judges have used for years. These are designed for exactly this job and it shows — a submission API, dozens of language definitions maintained for you, per-submission CPU-time and memory limits, stdin/expected-output comparison, and a queue. If your product is fundamentally "accept code, run against test cases, return a verdict," a judge gives you 80% of the domain logic on day one, and that's an enormous head start over building it. The trade is that you inherit its execution model — typically containers or cgroup-confined processes on a host you operate, so the shared-kernel caveats above apply and the hardening is yours to do. Check the license before you build a commercial product on one; several projects in this space have licensing terms that surprise people at exactly the wrong moment.
- Best fit for managed APIs: small teams shipping fast, products where execution isn't the differentiator, and anyone who would rather pay a bill than run a fleet.
- Best fit for self-hosted judges: competitive-programming platforms, classic autograders with fixed test-case semantics, and teams who want the domain model handed to them and are prepared to harden the host.
Rolling your own
Some of the best education platforms run bespoke execution infrastructure, and it's a legitimate choice — at a certain scale, and with a certain shape of curriculum, nothing off the shelf fits. But be clear-eyed about what the project actually is. The part everyone estimates is the sandbox: launch a process, apply cgroups and seccomp, capture output. That's the easy tenth. The other nine tenths are the fair queue that behaves under a 40× deadline spike, autoscaling that provisions ahead of the lecture rather than after it, the orphan reaper, the image build pipeline across a dozen runtimes, per-language resource profiles, log capture and retention, the egress policy, multi-region residency, and the on-call rotation that owns all of it in perpetuity.
The tell that you should build: execution latency or cost is your product's actual differentiator, you have a platform team, and you've already outgrown two vendors. The tell that you shouldn't: you're building it because evaluating vendors felt slower than starting, which is true for about three weeks and false forever after.
The five approaches, side by side
- Isolation boundary — Browser/WASM: the student's own tab and the browser's sandbox; strong for you (nothing runs on your infra) and worthless for grading (they control it). Container runner: namespaces and cgroups over one shared host kernel; a kernel bug is a node-wide event. MicroVM platform: a per-execution guest kernel under hardware virtualization with a minimal device model — the strongest practical boundary for code you didn't write. Managed sandbox API: varies by vendor from gVisor to Firecracker; establish which in writing. Self-hosted judge: usually containers or cgroup-confined processes, so shared-kernel caveats apply.
- Fork bombs and accidental DoS — Browser/WASM: the student's own tab hangs, which is instant, self-explaining feedback and costs you nothing. Container runner: contained only if you explicitly set `pids.max`, memory limits, CPU quota, and disk quotas; the default configuration is not safe at a deadline. MicroVM platform: capped by the guest's own baked RAM and vCPU, so the worst case is one dead VM plus a nonzero exit code. Managed API: usually handled, but ask what a 30,000-process fork bomb does to your other concurrent sandboxes. Self-hosted judge: generally good — per-submission CPU/memory/process limits are the core competency of these tools.
- Language and runtime breadth — Browser/WASM: whatever has been compiled to WASM; no `fork`, no raw sockets, limited native extensions, and a hard stop at the systems curriculum. Container runner: anything with a Dockerfile, which is effectively everything, and adding one is a merge request. MicroVM platform: a full Linux guest, so anything that runs on Linux runs here, including multi-process programs, real databases, and kernel-adjacent coursework. Managed API: vendor's template catalogue plus whatever custom images they let you build. Self-hosted judge: dozens of curated languages maintained for you, less flexible for anything outside the submit-and-verdict model.
- Cold-start feel — Browser/WASM: instant after the initial runtime download, which can be tens of megabytes on a phone. Container runner: fast when the image is cached on the node, and much slower on a cold node during a scale-out, which is precisely when the deadline spike arrives. MicroVM platform: with snapshot-restore this is a non-issue — on PandaStack 179ms p50, ~203ms p99, restore itself ~49ms, with the ~3s cold boot paid once at bake time; without snapshot-restore, a cold VM boot is far too slow to put in front of a Run button. Managed API: generally advertised as fast; measure it yourself, cold, in your region, at your spike concurrency. Self-hosted judge: queue latency usually dominates process startup, so it's a capacity question, not a startup one.
- Cost model — Browser/WASM: effectively zero marginal cost, which is unmatched and the reason to use it wherever it fits. Container runner: cheapest server-side option per run, but you pay for provisioned node capacity including the six quiet days, unless your autoscaling is genuinely good. MicroVM platform: more memory per execution than a container, offset by near-zero idle when executions are short-lived and TTL-reaped; self-hosting trades a per-second bill for hardware plus ops. Managed API: metered per-second or per-invocation with no idle cost — model the deadline burst, not the average, and check current pricing pages. Self-hosted judge: your servers, your bill, and capacity sized for the spike sits idle most of the week.
- Operational burden — Browser/WASM: almost none server-side; your work is packaging runtimes and supporting browser quirks you can't reproduce. Container runner: substantial and ongoing — cluster, autoscaling, images, hardening, and the person who understands why each seccomp rule exists. MicroVM platform: highest if self-hosted (KVM hosts, agents, networking, snapshot storage), near-zero if you use a hosted control plane; being able to choose is the point of an open-source core. Managed API: lowest by design, in exchange for a vendor dependency and someone else's roadmap. Self-hosted judge: moderate — the domain logic is handed to you, the host hardening and scaling are not.
Concretely: wiring an autograder
Here's the server-side loop with PandaStack's Python SDK, so the shape is concrete rather than abstract. One submission gets one microVM, the VM is destroyed when the block exits, and the guest never holds anything worth stealing — no roster, no database URL, no other student's work, and no answer key for assignments the student hasn't reached yet.
from pandastack import Sandbox
# Pin the grading environment to a BAKED template, never a floating "latest".
# A regrade in week 12 has to reproduce the mark you gave in week 3, and
# "we upgraded numpy" is not a sentence you want to say to an appeals committee.
TEMPLATE = "code-interpreter"
def grade(student_id: str, code: str, tests: str) -> dict:
"""One submission, one microVM, destroyed when the block exits."""
with Sandbox.create(template=TEMPLATE, ttl_seconds=120) as sbx:
sbx.filesystem.write("/workspace/submission.py", code)
sbx.filesystem.write("/workspace/run_tests.py", tests)
# Step 1: does it run at all? Syntax and import errors are the single
# most common failure mode, and they deserve a kind, specific message
# rather than a wall of test-runner output.
r = sbx.exec("python3 /workspace/submission.py", timeout_seconds=10)
if r.exit_code != 0:
return {
"student": student_id,
"status": "crashed",
"stdout": r.stdout[-4000:],
"stderr": r.stderr[-4000:], # this is the feedback they read
"ms": r.duration_ms,
}
# Step 2: grade it, on its own timeout -- a correct-but-slow solution
# is a different grade from one that never returns at all.
t = sbx.exec("python3 /workspace/run_tests.py", timeout_seconds=30)
return {
"student": student_id,
"status": "passed" if t.exit_code == 0 else "failed",
"stdout": t.stdout[-4000:],
"stderr": t.stderr[-4000:],
"ms": t.duration_ms,
}
# The VM, its page cache, its guest kernel, and whatever the submission
# forked, wrote, or downloaded all stop existing together. There is no
# cleanup script whose correctness depends on the student's cooperation.Now the part that actually decides whether your platform survives a deadline: grading the whole cohort. The submissions are independent, so this is embarrassingly parallel — the only real question is how many VMs you're willing to hold at once, which is a memory question about your fleet rather than a courage question.
import concurrent.futures as cf
def grade_class(submissions: list[dict], tests: str, lanes: int = 32) -> list[dict]:
"""Fan a cohort out across independent microVMs.
`lanes` is a throughput dial, not a safety control -- isolation is per-VM
regardless of how many run concurrently. Size it to the memory on your
fleet: each guest holds its baked RAM for its whole life, so 300 concurrent
2 GiB VMs is a capacity decision somebody should make on purpose.
"""
results: list[dict] = []
with cf.ThreadPoolExecutor(max_workers=lanes) as pool:
futures = {
pool.submit(grade, s["student_id"], s["code"], tests): s["student_id"]
for s in submissions
}
for fut in cf.as_completed(futures):
sid = futures[fut]
try:
results.append(fut.result())
except Exception as exc:
# One pathological submission must never fail the batch.
# Record it, keep draining, look at it in the morning.
results.append({
"student": sid,
"status": "error",
"stderr": repr(exc),
})
return results
# Queue discipline matters more than raw concurrency during a spike. Grade
# newest-first so the student refreshing at 11:58pm sees SOMETHING, show an
# honest queue position instead of a spinner, and let the 3am stragglers drain
# behind them. A fair queue with a truthful ETA beats a fast one that times out.Whatever backend you pick, put a second layer of limits inside the execution environment too. The isolation boundary is what protects your host and the other students; these limits are what stop one submission from eating its own VM before your grading timeout fires — and, just as usefully, they turn "the grader hung" into a clean exit code you can put in feedback.
# Runs INSIDE the guest, as a dedicated unprivileged user -- RLIMIT_NPROC is
# per-UID, so if the harness shares a UID with the submission, the harness is
# the process that loses the race.
ulimit -u 64 # max processes: the fork-bomb fence
ulimit -v 2097152 # 2 GiB of address space, in KiB
ulimit -f 262144 # 256 MiB max file size -- stops `cat /dev/zero > out`
ulimit -c 0 # no core dumps filling the rootfs
# Wall-clock fence with a hard kill after a grace period, because "ignores
# SIGTERM" is exactly the kind of program being graded here.
timeout --signal=TERM --kill-after=5s 10s \
python3 /workspace/submission.py < /workspace/stdin.txt
case $? in
0) echo "OK" ;;
124) echo "TIME LIMIT EXCEEDED" ;; # timeout(1) fired
137) echo "KILLED -- OOM, or the --kill-after fence" ;;
*) echo "RUNTIME ERROR ($?)" ;;
esac
# What this does NOT do: stop the submission reaching the network, or reading
# any file the grading user can read. Egress belongs on the host side of the
# guest's network namespace, where student code cannot flush the rules -- and
# the answer key belongs on a machine the submission never touches at all.When a sandbox is overkill
Being an honest broker means saying when you should skip all of this. If your exercises are small, self-contained, and not graded — a docs playground, a "try it" widget, an intro tutorial — run them in the browser and spend the saved effort on the curriculum. Nobody has ever chosen a learning platform because of its execution backend, and a great exercise on Pyodide beats a mediocre one on a hypervisor.
Likewise, if you fully control the code and it isn't learner-authored, a subprocess is simpler and faster; don't reach for a VM to run code you wrote. If your "grading" is really pattern-matching against an expected string, a parser will do. And if you have thirty students in one course who you know by name, a hardened container with hard resource limits is an entirely defensible boundary — the fork bomb is still the risk, and `pids.max` still handles it. Reach for stronger isolation when the population becomes open or anonymous, when the result becomes an academic record, or when the curriculum needs a real kernel. Those three thresholds are the actual decision, not the technology.
The bottom line
There's no single best code execution sandbox for education — there's a best one for your population, your curriculum, and your spike. Browser execution is unbeatable where it fits and cannot be trusted for grades. Containers are cheap and ubiquitous and share a kernel, which is fine for a known cohort with real cgroup limits and uncomfortable for an open platform. MicroVMs give each execution its own kernel, which is the right boundary for code from strangers and for marks that have to survive an appeal — provided the platform uses snapshot-restore, because a cold VM boot in front of a Run button is a design that will decay back into a shared pool within a quarter. Managed APIs and self-hosted judges are both good ways to not build this, differing in whether you want a primitive or the whole domain model.
PandaStack's position, stated plainly by an interested party: we're an open-source Firecracker platform you can self-host on your own KVM hosts, where every create is a snapshot restore at 179ms p50 (~203ms p99, restore step ~49ms), copy-on-write forks run 400–750ms same-host for fanning a warmed lab environment across a cohort, each sandbox has its own netns for host-side egress control, and TTL reaping means a wedged submission is collected rather than billed. That fits graded, open, or systems-heavy platforms, and it's the wrong pick if a hardened container already satisfies your threat model. Don't decide from this post, or from any roundup written by someone with a horse in the race. Take your top two, and before the next term starts, run a load test that replays a real deadline: your actual submissions, at your actual spike, from your actual region — including a deliberate fork bomb, a 40 GB allocation, and an infinite loop. What the platform does in those three minutes is what you're buying.
Frequently asked questions
What is the best code execution sandbox for an education platform?
It depends on three thresholds rather than on the technology. If your learners are an identified, enrolled cohort and the work isn't a formal academic record, a properly hardened container per submission — with pids, memory, CPU, and disk limits actually configured — is defensible and cheap. If your platform is open to anyone, if results become marks that must survive an appeal, or if the curriculum needs real processes and a real kernel, a microVM per execution is the right boundary because student code gets its own guest kernel instead of sharing yours. Browser-based WASM execution is excellent for interactive tutorials and unsuitable for grading, since the student controls the environment. Most serious platforms run browser execution for tutorials and a server-side sandbox for anything graded.
How do you stop a student's fork bomb from taking down the grader?
Layer two defences, because they solve different problems. Inside the execution environment, cap processes per UID — a pids cgroup limit or RLIMIT_NPROC via `ulimit -u` — along with memory, file size, and CPU time, and run submissions as a dedicated unprivileged user so the cap isn't shared with your harness. Outside it, make the blast radius one disposable environment: with a microVM per submission, a fork bomb exhausts that guest's own baked RAM and process table and nothing else, and the machine is deleted seconds later. With shared-kernel containers this only holds if the limits are explicitly configured; the default configuration is not safe on a deadline night. Also add a wall-clock timeout with a hard kill after a grace period, so a program that ignores SIGTERM still terminates and produces a clean exit code you can turn into feedback.
Can students just run code in the browser with WASM instead of a server sandbox?
For interactive tutorials, docs examples, and intro exercises, yes, and it's often the best choice — execution runs on hardware you don't pay for, so marginal cost is zero and deadline spikes are somebody else's capacity problem. The limits are what's been compiled to WebAssembly: no real fork or multi-process work, no raw sockets, limited native extensions, no database server, and a hard stop where the systems curriculum begins. The decisive limitation for education is trust: the student controls the browser, so a passing test result means the tests reported passing on a machine they own. Use browser execution for learning and feedback, and re-run anything graded server-side in an environment you control.
How do you keep autograding reproducible months later for appeals?
Treat the grading environment as a versioned artifact, not a live system. Pin each assignment to an immutable, baked image or VM snapshot with the exact interpreter patch version and library versions, and never grade against a floating latest tag. Store the image reference alongside the mark so a regrade restores exactly the machine that produced it. Prefer CPU-time limits over wall-clock ones where the mark depends on performance, since wall-clock varies with host load and makes borderline results non-deterministic. Also record the submission bytes, the test-suite version, and the exit code and output you scored, so an appeal can be resolved by re-running the original rather than by argument.
How should an education platform handle the deadline spike in submissions?
Design for the spike explicitly, because the average tells you nothing: nothing happens for six days and then a semester's load arrives in nine minutes. Provisioning latency is the critical property — if a fresh environment takes tens of seconds, you'll pool environments to hide it, and pooled environments get reused across students, which gives back the isolation you paid for. Snapshot-restore avoids that trade: on PandaStack every create restores a pre-booted snapshot at 179ms p50 and roughly 203ms p99, so a fresh machine per submission stays cheap even under burst. Beyond provisioning, queue discipline matters more than raw throughput — grade newest-first during the spike, show an honest queue position instead of a spinner, and make sure one pathological submission fails alone rather than failing the batch. Load-test by replaying a real deadline with real submissions before the term starts.
49ms p50 cold start. Fork, snapshot, and scale to zero.