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");
},
});| Option | Type | Description |
|---|---|---|
image | string | { 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. |
shell | string | Shell 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
});| Option | Type | Description |
|---|---|---|
env | Record<string, string> | Environment variables set at container startup. |
hooks | SandboxHooks | Observability hooks (e.g. otelHooks(tracer) from @alineo-labs/otel). |
shell | string | Shell 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 } | nullReturns 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
shandbase64. Both are present in Debian, Ubuntu, and Alpine. Passshell: "/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 viagetSnapshotand rebuilds automatically. - Concurrent
rebuild()calls are not deduplicated. For production use, callrebuild()from a single coordinated process such as a deploy script.