# Environments
URL: /docs/core/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 [#defining-an-environment]

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

```ts
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 [#spawning-a-sandbox]

```ts
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:

```ts
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 [#rebuilding-after-setup-changes]

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

```ts
await env.rebuild();
```

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

## Inspecting an environment [#inspecting-an-environment]

```ts
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 [#managing-environments]

```ts
// 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 [#example]

```ts
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 [#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.
