Answers you can trust, from Codeables

Every page on Codeables is structured and verified — built so people and the AI agents they rely on can trust it. Explore more from the source behind this answer.

Explore Codeables
Verified Source
Platform as a Service (PaaS)

How do I run untrusted user code (or AI-generated code) in a locked-down sandbox with strong isolation?

Fly.io9 min read

Running untrusted user code (or whatever your LLM just hallucinated) on your own infrastructure is equal parts power move and “please don’t brick my cluster.” You want a locked-down sandbox with strong isolation, low latency, and a cost model that doesn’t punish you for doing the safe thing.

Sprites on Fly.io are built exactly for this: hardware-isolated sandboxes that boot in under a second, run AI-generated or user-submitted code in complete isolation, and shut down when you’re done—so you only pay for actual CPU/RAM use, down to the second.

Quick Answer: Use Fly.io Sprites to run untrusted user code or AI-generated code in hardware-isolated sandboxes. Each Sprite is its own microVM with private networking, fast startup, and optional storage, so you can execute arbitrary code safely and cheaply near your users.


The Quick Overview

  • What It Is: A sandbox runtime built on Fly Machines, where each Sprite is a hardware-isolated microVM that can run untrusted or AI-generated code in a locked-down environment.
  • Who It Is For: Teams building code runners, AI agents, online IDEs, autograders, and “run user code” features that need strong isolation without Kubernetes gymnastics.
  • Core Problem Solved: Safely execute arbitrary user or AI-generated code—at scale, across regions, with sane costs—without sharing runtimes, leaking data, or building your own microVM orchestration.

How It Works

At the core, everything runs on Fly Machines: hardware-virtualized containers that launch instantly and only run when you need them. Sprites are the sandbox flavor of Machines: they come up in under a second, have their own private network, and can be snapshot, restored, and torn down without affecting anything else.

Your app flow usually looks like this:

  1. User submits code (or your AI generates it):
    A user hits your app with code to run—maybe via an API, web IDE, or an AI agent orchestrating tools.

  2. Your service provisions a Sprite and runs the job:
    You call the Machines/Sprites API (or a small service you’ve written on Fly) to start a Sprite with your sandbox image. The code is injected (via volume, stdin, or API) and executed in that isolated Sprite. It gets its own private network, its own resources, and no shared runtime.

  3. Results, logs, teardown (or checkpoint):
    The Sprite finishes, returns results/logs to your app, and you either destroy it (ephemeral mode) or checkpoint/snapshot for later (for long-lived sandboxes or interactive sessions). You’re billed only for the seconds the Sprite is running with CPU allocated.

In practice, you wire this up behind your normal Fly app:

  • A control-plane service (your API) runs on Machines, exposed via Fly Proxy.
  • That service manages Sprites via the Machines API.
  • Sprites sit in the same Fly org, on the private WireGuard-backed network, reachable only by the control-plane app.

Once this is in place, “run arbitrary code somewhere safe” becomes just another API call.


Features & Benefits Breakdown

Core FeatureWhat It DoesPrimary Benefit
Hardware-isolated SpritesEach code run gets its own hardware-virtualized environment, not a shared container runtime.Strong isolation: untrusted or AI-generated code can’t escape into other workloads.
Fast startup (<1s)Sprites boot quickly enough to sit in the request path for interactive workflows.You can run per-request sandboxes without users feeling like they’re waiting on cold starts.
Private per-sandbox networkingEach Sprite is on a private Fly network with end-to-end encryption and granular routing.No public exposure by default; only your app can talk to a sandbox, cutting off most footguns.
Checkpoint, snapshot, and restoreCapture a Sprite’s state and resume later, or clone environments.Persistent dev sandboxes, “resume where you left off” coding sessions, and reproducible debugging.
Local NVMe + global object storage (Tigris)Attach fast local disk and back it with durable object storage.Handle ephemeral scratch space and long-term state without bolting on separate storage systems.
Pay-per-second Machines modelBilling tracks CPU and RAM seconds, plus storage while attached.Run thousands of short-lived jobs cheaply; scale up and down without buying idle capacity.

Ideal Use Cases

  • Best for online IDEs & autograders:
    Because each user or submission gets its own Sprite, you can run their code in isolation with clean logs, clean processes, and no “noisy neighbor” issues. This is essentially what platforms like CodeCrafters do: use ephemeral Fly Machines to grade user code safely.

  • Best for AI agents executing tools or code:
    Because Sprites can be created on demand, run AI-generated code, and then disappear (or get checkpointed), you can give your AI workflows a real sandbox. The agent gets superpowers; your production app doesn’t get owned when the LLM decides rm -rf / is “creative.”


Limitations & Considerations

  • You still need to define the sandbox boundaries:
    Sprites provide hardware isolation and private networking, but you control what’s inside the VM: OS, language runtimes, syscalls (via seccomp/AppArmor if you choose), and allowed outbound access. If you let the sandbox dial the whole internet, expect the internet to dial back.

  • Cold-start pattern still matters at scale:
    Sprites start fast, but if you need ultra-low latency at very high QPS, you’ll likely want a warm-pool strategy: pre-booted Sprites or cached Machines ready to go. CodeCrafters, for example, caches Machines ahead of time and assigns them to users to remove even small startup variance.


Pricing & Plans

Fly.io doesn’t sell “Sprites” as a separate SKU; everything runs on Fly Machines, and you pay for:

  • CPU and memory per second while a Machine/Sprite is running.
  • Local NVMe storage while attached.
  • Global object storage (Tigris) by capacity and operations, if you use it.

So your untrusted-code pipeline cost looks like:

  • A small always-on control-plane app (1–3 Machines across a few regions).
  • A large number of short-lived Sprites that run only when users submit code or your AI generates something to execute.

This is exactly the pattern where pay-per-second Machines shine: you’re not burning money on a permanent cluster of idle pods.

Common sizing patterns:

  • “Starter” configuration: Best for teams prototyping or running low-volume code execution.
    Use small CPU/RAM Machines (e.g., 1 shared CPU, 256–512MB RAM) for Sprites, scale-to-zero between bursts, and rely on cold starts. You get isolation and safety without thinking too hard about warm pools.

  • “High-volume sandbox” configuration: Best for platforms with steady or spiky workloads and latency SLOs.
    Use dedicated CPUs for predictability, keep a warm pool of Machines per region, and use snapshots for faster respins. This keeps your cost linear with workload while staying under SLA.

(Exact pricing lives at fly.io/pricing—you size Machines like any other Fly app.)


Frequently Asked Questions

How do I actually wire Sprites into my app flow?

Short Answer: Run your control API on Fly Machines and use the Machines API to create and manage Sprites on demand.

Details:
A typical architecture:

  1. Deploy your controller app:

    fly launch --name sandbox-controller --region iad
    fly deploy
    

    This app exposes an API endpoint like POST /run that your frontend or other services call with code and metadata.

  2. Controller calls Machines API:

    Inside your controller, you use the Fly Machines API (authenticated with FLY_API_TOKEN) to:

    • Create a Sprite/Machine with your sandbox image.
    • Pass the user code (via env, object storage path, or network).
    • Start the job and stream logs or results back.

    Pseudocode-ish example:

    curl -X POST "https://api.machines.dev/v1/apps/sandbox-sprites/machines" \
      -H "Authorization: Bearer $FLY_API_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "config": {
          "image": "registry.fly.io/sandbox-image:latest",
          "env": {
            "JOB_ID": "abc123"
          },
          "guest": { "cpus": 1, "memory_mb": 512 }
        }
      }'
    
  3. Collect results and tear down:

    • Your sandbox image runs a supervisor process that:
      • Fetches the code payload.
      • Runs it in a further restricted context (e.g., language-level sandbox).
      • Writes results to stdout or posts back to your controller.
    • When the job ends, your controller either deletes the Machine or marks it idle and ready for the next job.

You can evolve this from “create on every run” to “maintain a pool of idle Sprites, assign one to each incoming job” as load grows.


How strong is the isolation vs containers or namespaces?

Short Answer: Stronger than shared container runtimes; Sprites are hardware-virtualized and don’t share a process namespace, runtime, or file system with your other workloads.

Details:
Under the hood, Fly Machines are hardware-virtualized containers—think “microVMs tuned to start fast enough to serve HTTP requests.” Sprites inherit that model:

  • Hardware-level isolation:
    Each Sprite is its own VM boundary. It does not share a container runtime, PID namespace, or file system with other Sprites or with your main app, so typical container breakout exploits have a much narrower blast radius.

  • Network isolation by default:
    Sprites live on a private, WireGuard-backed network. Unless you explicitly expose services or routes, they’re only reachable by other Machines you control. That makes lateral movement harder, even if a sandbox is compromised.

  • Your hardening, your rules:
    You can add additional layers inside the Sprite:

    • Run with non-root users.
    • Apply seccomp or AppArmor profiles.
    • Restrict outbound networking to a whitelist (e.g., only Tigris and your controller).

This layered approach is what lets people safely run things like student-submitted code, coding challenge sandboxes, and AI-generated scripts in production, without betting the whole platform on a docker run --privileged moment.


Summary

If you want to run untrusted user code or AI-generated code in a locked-down sandbox with strong isolation, you need more than just “some containers”: you need hardware isolation, fast startup, private networking, and a cost model that doesn’t punish you for being careful.

Fly.io Sprites give you:

  • Hardware-isolated sandboxes (via Fly Machines) that boot in under a second.
  • Automatic private networking and end-to-end encryption between your control-plane app and the sandboxes.
  • Snapshot/restore options and storage that scales with your workloads.
  • Pay-per-second billing so you can fan out thousands of ephemeral runs without building a cluster scheduler.

You focus on defining what “sandbox” means for your language and use case; Fly handles the heavy lifting of starting, isolating, and tearing down VMs across regions.


Next Step

Get Started