AI sandbox: isolation, code execution, and costs

AI sandbox: isolation, code execution, and costs

# ai# devops# tutorial# programming
AI sandbox: isolation, code execution, and costsYura Oak

Learn what an AI sandbox is, how Docker and Firecracker isolate agent code, and what it costs. Includes a Lizard SDK example with time limits and cleanup.

Originally published on Lizard (lizard.build).

An AI sandbox is an isolated environment where an AI agent can run code, install packages, and work with files. Its job is to limit what that code can reach: the host machine, other users' work, credentials, and external services.

Before choosing a runtime for your agent, check how it isolates code, whether it supports your workload, and what it charges while running. This guide explains those choices and shows a small execution flow with Lizard SDK.

What is an AI sandbox?

In an agent application, the model proposes an action and your application decides where and how to run it. A sandbox supplies the computer for actions that need code execution. The model might write a Python script, edit a repository, or start a web app there.

The term also describes business programs for testing AI products. Here, it means the runtime that executes an agent's code.

The runtime needs more than a folder for each user. A useful design separates processes and files, caps CPU and memory, and gives the application a way to stop work. Credentials, network access, and persistent storage need their own rules.

For example, an agent that analyzes an uploaded CSV should receive that file and enough compute to process it. It should have no reason to read the web server's environment variables or another customer's uploads.

How an AI sandbox works

A typical request follows this path:

  1. Your backend receives a task and asks the model to produce code or tool calls.
  2. Your backend creates a sandbox and copies in the files that task needs.
  3. The sandbox runs the code. Your backend collects stdout, stderr, files, and the exit code.
  4. Your backend checks the result before showing it to the user or sending it to another service.
  5. Your backend saves the required output and stops the sandbox.

Keep the model provider key and the sandbox management key in your backend. Giving generated code a management key would let it request more resources using your account.

A coding session may need the same sandbox for several turns. A one-off calculation can use a fresh sandbox per job. Decide how long to retain state and which user owns it before you add reuse.

Docker, gVisor, and Firecracker: what changes?

These technologies provide different isolation boundaries. The rest of the system still determines which files, networks, and credentials the code can access.

Approach Where the boundary sits What to check
Standard Docker container Linux namespaces and resource controls separate processes; containers share the host kernel. Capabilities, privileged mode, host mounts, daemon access, and host patching.
gVisor A userspace application kernel handles application system calls, reducing direct exposure to the host kernel. System call compatibility and performance for your workload.
Firecracker microVM A virtual machine has its own guest kernel and uses KVM for hardware virtualization. Guest and host patching, network policy, mounted data, and lifecycle controls.

Docker can suit workloads with a known trust level and a carefully configured host. Its security depends on how you run it; exposing the Docker socket or broad host mounts changes what the workload can control. See Docker's security documentation.

gVisor adds a userspace kernel boundary, with compatibility tradeoffs. Firecracker uses a small virtual machine monitor and a limited device model. Neither removes the need to protect secrets or restrict access to sensitive services.

Lizard Sandboxes use Firecracker. Older volumes or snapshots, and a fallback after a failed Firecracker start, can use the container runtime. Check getInfo().runtime if your application requires a microVM. The example below rejects any other runtime. See the Lizard Sandboxes documentation.

What isolation does and does not protect

A sandbox limits the effect of code execution. Your application still has to decide which actions a user or agent may take.

Credentials and network access

Anything you copy into the sandbox becomes available to the workload under the permissions you give it. Avoid copying your whole .env, SSH directory, or cloud credentials. If a task needs an external API, give it a short-lived credential with narrow permissions, or route the operation through a backend tool that checks each request.

Isolation does not imply blocked internet access. If code can read a secret and make outbound requests, it may be able to send that secret elsewhere. Choose a provider and configuration that meet your outbound network requirements. Do not assume a network allowlist exists without checking the product's controls.

Users and stored files

Assign each sandbox to an authenticated user or job. Check ownership on every request to execute code, download a file, or reopen a session. A guessed sandbox ID must not grant access.

Keep persisted files separate by user too. Reusing a writable workspace across unrelated customers can expose data even when each execution gets a fresh virtual machine.

Time limits and returned output

Set an execution timeout and a sandbox lifetime. A timeout in the client can stop waiting without stopping the remote process. Cleanup and a server-side expiry protect against different failures.

Treat returned files and text as untrusted input. Validate JSON against the shape your application expects. Escape text when rendering it, and avoid serving generated HTML on the same origin as your account dashboard.

For agents that skip interactive approvals, these controls matter before the first command runs. Our Claude Code permission guide covers that workflow.

Run a Python task in Lizard Sandboxes

This example uses Node.js as the backend controller and Python inside the sandbox. It runs a fixed calculation so you can check the execution path before adding a model. It does not call an LLM.

Use Node.js 22 or later, a Lizard project, and a Lizard API key. Set LIZARD_API_KEY and LIZARD_PROJECT_ID in your backend environment. Keep both out of browser code; the API key belongs in your secret store.

Install the JavaScript client:

npm install @lizard-build/sdk@0.1.69
Enter fullscreen mode Exit fullscreen mode

Save this as run-task.mjs:

import { Lizard } from "@lizard-build/sdk";

const project = process.env.LIZARD_PROJECT_ID;
if (!project || !process.env.LIZARD_API_KEY) {
  throw new Error("Set LIZARD_PROJECT_ID and LIZARD_API_KEY");
}

const lizard = new Lizard({ project });
const sandbox = await lizard.create("base", {
  size: "small",
  timeoutMs: 120_000,
});

try {
  const info = await sandbox.getInfo();
  if (info.runtime !== "firecracker") {
    throw new Error("This task requires the Firecracker runtime");
  }

  const script = [
    "import json",
    "values = [12, 8, 5]",
    "print(json.dumps({'total': sum(values)}))",
  ].join("\n");

  await sandbox.fs.write("/tmp/task.py", script);
  const result = await sandbox.process.exec(
    ["python3", "/tmp/task.py"],
    { user: "user", timeoutMs: 30_000 },
  );

  if (result.exitCode !== 0 || result.truncated) {
    throw new Error("Task failed or returned incomplete output");
  }
  if (Buffer.byteLength(result.stdout, "utf8") > 4096) {
    throw new Error("Task output exceeds the application limit");
  }

  const output = JSON.parse(result.stdout);
  if (
    output === null ||
    typeof output !== "object" ||
    Array.isArray(output) ||
    Object.keys(output).length !== 1 ||
    output.total !== 25
  ) {
    throw new Error("Unexpected task output");
  }

  console.log(output);
} finally {
  await sandbox.kill();
}
Enter fullscreen mode Exit fullscreen mode

Run it from your backend machine:

node run-task.mjs
Enter fullscreen mode Exit fullscreen mode

On October 17, 2026, we ran this exact script from a Lizard sandbox using the codex template and Node.js 22.23.2. It created a Small Firecracker sandbox from the base template, ran Python as user, printed { total: 25 }, and completed cleanup with exit code 0.

The SDK reads LIZARD_API_KEY from the controller's environment. This code does not pass it into the sandbox that executes Python.

The argument array executes Python without shell interpolation. Python can still perform whatever operations the sandbox allows. The 30-second command timeout limits how long the client waits; the two-minute sandbox lifetime sets a separate expiry. The finally block requests cleanup even if output validation fails. See the Lizard SDK reference.

To turn this into an agent tool, replace the fixed script with the code your model returns and define a result schema for that task. Keep authorization, resource limits, and output checks in the backend. A successful exit code alone does not prove that the answer is correct.

What can you build with an AI sandbox?

Data analysis: process a user upload, run Python, and return a chart or a small JSON result. Limit file size and execution time, then retain only the output the user needs.

Coding agents: check out a task-specific copy of a repository, edit files, and run tests. Review the diff and test results before a separate workflow merges or deploys changes.

App builders: install dependencies, start a development server, and provide a private preview. Keep the preview separate from your dashboard's origin and authentication. Our Lovable-style app builder guide shows the wider application flow.

Browser agents: use a runtime with a browser and desktop when the task needs UI interaction. Treat any logged-in session as a credential with access to that account.

Lizard Sandboxes offer shell commands, file access, and private HTTPS previews. Browser and desktop work uses the desktop template. A development preview and a deployed application have different lifetimes; choose which one your user needs. See Lizard Sandboxes.

How much does an AI sandbox cost?

Start with total running time, including time between commands. Then add storage, model calls, and any other services your application uses.

Lizard lists these compute rates as of October 17, 2026:

Size vCPU RAM Compute per hour
Small 2 4 GB $0.0162
Medium 4 8 GB $0.0324
Large 8 16 GB $0.0648

Lizard meters compute per second while a sandbox runs, including idle time. Lizard Pro costs $19 per month and includes $19 of usage; excess usage is pay as you go. Persistent Volumes cost $0.14 per GB per month. Check the current pricing before budgeting.

For 1,000 Small jobs that each keep a sandbox running for five minutes:

1,000 × (5 / 60 hours) × $0.0162 = $1.35 in compute usage
Enter fullscreen mode Exit fullscreen mode

That is the compute portion of the workload. Your total bill still includes the Pro subscription and any usage beyond its allowance. Model tokens are a separate cost.

If those jobs each leave a sandbox running for an extra hour, they add $16.20 in compute usage. An explicit lifetime and reliable cleanup can matter more than a small difference in hourly rates.

How to choose an AI sandbox

Test with the job your users will run. A quick shell command tells you little about a repository build or a browser session.

Record startup time, dependency installation time, total task time, failures, and total running seconds. Check the slowest requests as well as the median. Our E2B, Daytona, and Modal comparison gives a starting point for comparing execution workflows.

Before adopting a runtime, answer these questions:

  • What separates one customer's code and files from another's?
  • Which secrets and external services can generated code reach?
  • Can the runtime run the languages, packages, and browser tools the task needs?
  • What happens when the controller crashes or loses its connection?
  • Which files survive shutdown, and how do you back them up?
  • What will the full workload cost with idle time, storage, and model calls?

For a first implementation, start with one task, one temporary sandbox, and a small validated result. Create Lizard Sandboxes, measure that flow, then add persistence or longer sessions when the application needs them.