Concepts

Environments

Define a named setup recipe once, snapshot it, and spawn cheap isolated sandboxes from it on demand.

An environment is a named sandbox configuration with a setup recipe. It's built once — installs packages, writes config, compiles assets — snapshots the result, and restores from that snapshot on every subsequent call. Setup never runs again unless you explicitly rebuild.

Each sandbox spawned from an environment is fully isolated. Changes in one do not affect others or the snapshot.

Defining an environment

client.environment(name, opts) returns an Environment object. No I/O happens at this point.

const env = client.environment("python-data-science", {
  image: "debian:bookworm-slim",
  resources: { cpu: "500m", memory: "512Mi" },
  setup: async (sb) => {
    await sb.exec(
      "apt-get update -qq && apt-get install -y python3-pip --no-install-recommends -q",
    );
    await sb.exec("pip install --quiet numpy pandas matplotlib");
  },
});
OptionTypeDescription
imagestring | { uri, auth? }Container image. Same format as SandboxOptions.image.
resources{ cpu: string; memory: string; gpu?: string }Applied to both the build sandbox and each spawned sandbox.
setup(sb: SandboxHandle) => Promise<void>Runs once to configure the environment.
shellstringShell binary for all exec() calls in this environment. Defaults to "/bin/sh".

Spawning a sandbox

const sb = await env.sandbox();
try {
  await sb.exec("python3 -c 'import pandas; print(pandas.__version__)'").pipe(process.stdout);
} finally {
  await sb.close();
}

The first call builds and snapshots the environment. Every subsequent call restores from the cached snapshot. Concurrent first calls are safe — setup runs exactly once even if multiple callers race.

env.sandbox(extra?) accepts additional per-spawn options:

const sb = await env.sandbox({
  env: { PYTHONPATH: "/app", DEBUG: "1" },
  hooks: otelHooks(tracer),
  shell: "/bin/bash", // overrides EnvironmentOptions.shell for this sandbox only
});
OptionTypeDescription
envRecord<string, string>Environment variables set at container startup.
hooksSandboxHooksObservability hooks (e.g. otelHooks(tracer) from @alineo-labs/otel).
shellstringShell override for this sandbox only. Falls back to EnvironmentOptions.shell.

Rebuilding after setup changes

Call env.rebuild() to force a fresh build, discarding the cached snapshot:

await env.rebuild();

There is no automatic invalidation. Call rebuild() explicitly whenever your setup script changes.

Inspecting an environment

const info = await env.info();
// → { name, snapshotId, image, builtAt } | null

Returns null if the environment has never been built. builtAt is a Unix timestamp in milliseconds.

Managing environments

// List all cached environments
const envs = await client.environments.list();
// → [{ name, snapshotId, image, builtAt }, ...]

// Remove the ledger record for an environment
await client.environments.delete("python-data-science");

delete removes the ledger record only — it does not delete the server-side snapshot. Cleaning up orphaned snapshots is handled by OpenSandbox's TTL policy or via the OpenSandbox API directly.

Example

import { Sandbox } from "@alineo-labs/sandbox";
import { SQLiteAdapter } from "@alineo-labs/sqlite";

const client = new Sandbox({
  baseUrl: "http://localhost:8080",
  adapter: new SQLiteAdapter("./ledger.db"),
});

const env = client.environment("node-ci", {
  image: "debian:bookworm-slim",
  resources: { cpu: "1", memory: "1Gi" },
  setup: async (sb) => {
    await sb.exec("apt-get update -qq && apt-get install -y nodejs npm -q");
    await sb.exec("npm install -g typescript ts-node");
  },
});

// Two isolated CI jobs from the same environment
const [sb1, sb2] = await Promise.all([env.sandbox(), env.sandbox()]);

try {
  await Promise.all([
    sb1.exec("node --version").pipe(process.stdout),
    sb2.exec("tsc --version").pipe(process.stdout),
  ]);
} finally {
  await Promise.all([sb1.close(), sb2.close()]);
}

Limitations

  • Images must have sh and base64. Both are present in Debian, Ubuntu, and Alpine. Pass shell: "/bin/bash" if your setup commands require bash-specific syntax.
  • There is no automatic invalidation. If your setup function changes, the snapshot is stale until you call env.rebuild().
  • Snapshots live on the OpenSandbox server. If the server prunes a snapshot (TTL), env.sandbox() detects the missing snapshot via getSnapshot and rebuilds automatically.
  • Concurrent rebuild() calls are not deduplicated. For production use, call rebuild() from a single coordinated process such as a deploy script.