> ## Documentation Index
> Fetch the complete documentation index at: https://docs.auto.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Environments and Sandboxes

> Where agents run: isolated cloud sandboxes built from a declared base image, build steps, and cached per-session setup, with secrets injected at launch.

Every session runs inside its own isolated cloud sandbox, built from the agent's **environment** — a declared recipe of base image, build steps, per-session setup commands, resource limits, and environment variables. This page explains the sandbox model and each layer of the recipe conceptually; the field-by-field schema lives in [the environment reference](/reference/environments).

## The sandbox model

A sandbox is a fresh, single-session virtual machine. The harness and its tools run as a non-root workspace user (`user`, home `/home/user`); repositories mount under paths you declare (conventionally under `/workspace`). When the session ends, the sandbox goes away — durable state lives in git, in the provider surfaces the agent writes to, and in the session transcript.

The sandbox is the isolation boundary. That is why unattended operation works: by default every harness action is auto-approved inside the sandbox (see [approvals](#approvals) below), and the blast radius of any command is the sandbox itself plus whatever credentials you deliberately handed the session. GitHub API credentials never sit in the sandbox at all — they are minted per-call by the platform's brokered GitHub proxy (see [GitHub MCP](/runtime/github-mcp)).

Every sandbox image also carries the agent toolchain baked in at build time: pinned versions of Claude Code and Codex, plus `git` and `curl`. You never install a harness in your own steps.

## Layers of an environment

An environment recipe has three layers, distinguished by *when* they run and *how* they are cached:

| Layer          | Field   | Runs                                     | Cached                                     |
| -------------- | ------- | ---------------------------------------- | ------------------------------------------ |
| Base image     | `image` | —                                        | Preset or direct Docker/OCI base reference |
| Build steps    | `steps` | At image build                           | In the built image                         |
| Setup commands | `setup` | When a sandbox is prepared for a session | Snapshot + declared cache paths            |

### Base image: preset or direct reference

Use a named preset when one matches the repository:

```yaml theme={null}
image:
  kind: preset
  name: node24
```

`node24` and the legacy `default` alias resolve to `node:24-bookworm-slim`; explicit `node22` and its legacy `linux-node22` alias resolve to `node:22-bookworm-slim`. A direct base reference is also supported:

```yaml theme={null}
image:
  kind: base
  ref: debian:bookworm
```

Direct bases must satisfy the versioned Debian compatibility contract before auto's harness bootstrap and your build steps run. auto generates the single-stage Dockerfile; it does not import a Dockerfile from the repository. Follow [Validate an environment image](/guides/environment-images) to test the base, authored build/setup sequence, and repository smoke commands locally before apply.

### Build steps

`steps` are Dockerfile commands appended after the platform's harness bootstrap. Use them for system packages and pinned command-line binaries — anything every session should find already installed:

```yaml theme={null}
steps:
  - RUN apt-get update && apt-get install -y --no-install-recommends postgresql-client jq && rm -rf /var/lib/apt/lists/*
  - RUN npm install -g tsx
```

Build steps bake into a cached image, so sessions do not pay for them at start. Pin versions (and verify checksums for downloaded binaries) to keep the image reproducible — see [fragments](/concepts/fragments).

### Per-session setup

`setup` commands run when the sandbox is prepared, inside the agent's working directory (the first git [mount](/reference/mounts)), as the workspace user, with a 10-minute timeout per step. This is where repository-dependent work belongs — installing workspace dependencies, fetching browsers:

```yaml theme={null}
setup:
  - name: install-workspace-deps
    commands:
      - npm ci --prefer-offline
    cache:
      key: npm-workspace-v1
      files:
        - package-lock.json
      paths:
        - /home/user/.npm
setupCache:
  ttl: 7d
```

Setup is cached at two levels:

* **Declared cache paths.** Each step can name directories under `/home/user` to persist across sessions. Before the commands run, cached contents are restored; after they succeed, the paths are persisted back. The invalidation key is the step's `cache.key` (defaulting to the step name) combined with a hash of the listed `files` — change `package-lock.json` and the npm cache misses cleanly.
* **Snapshot reuse.** When an environment declares setup, the platform bakes a post-setup snapshot and reuses it for subsequent sessions until the `setupCache.ttl` window (default seven days) expires or the recipe changes.

Together these turn a multi-minute `npm ci` into a warm start for every session after the first.

## Resources

Environments can request CPU and memory for their sandboxes:

```yaml theme={null}
resources:
  cpuCount: 2
  memoryMB: 8192
```

`cpuCount` must be at least 1 and `memoryMB` at least 128; declare at least one of them if the block is present. Size for the heaviest thing the agent actually does — Docker-Compose-backed integration stacks want more headroom than a chat agent.

## Environment variables and secrets

Agents and environments both accept an `env` map. Values are plain strings or references into the project's encrypted [secret store](/reference/secrets):

```yaml theme={null}
env:
  NODE_ENV: production
  TS_AUTHKEY:
    $secret: tailscale-authkey
    optional: true
```

Secret values are envelope-encrypted at rest and resolved only at session launch. `optional: true` means a missing secret omits the variable instead of failing the launch — useful for capabilities that should degrade gracefully until an org secret exists. Variable names that the platform's own bootstrap reserves are rejected before launch, so agent env can never shadow the runtime's wiring.

Secrets configured for **injection** never enter the sandbox at all: the sandbox sees a placeholder, and the provider's network edge injects the real value into request headers for exactly the hosts the secret names (exact host match — no wildcards, and a parent domain does not cover its subdomains). Prefer injection for API keys whose only job is authenticating HTTP calls.

## Approvals

`approvals` sets the harness's permission posture inside the sandbox: `bypass` (the default when absent) auto-approves every harness action, so unattended sessions never stall on a permission prompt; `prompt` opts the environment back into the harness's own approval escalation for operator-attended use. Today only the `codex` harness consults it — `claude-code` always runs with permissions bypassed, relying on the sandbox boundary.

## Reuse: inline environments and fragments

An agent must resolve to exactly one environment after its imports merge. There are two ways to supply it.

**Inline** — declare the environment object directly on the agent. The compiler splits it out into a generated `environment` resource; the agent keeps only the name:

```yaml .auto/agents/digest.yaml theme={null}
name: digest
harness: claude-code
environment:
  name: digest-runtime
  image:
    kind: preset
    name: node24
```

**Fragment** — the conventional path for shared runtimes. Put the environment (plus any shared agent fields, like `harness`) in a fragment under `.auto/fragments/environments/` and import it from every agent that runs there:

```yaml .auto/fragments/environments/agent-runtime-base.yaml theme={null}
harness: claude-code
environment:
  name: agent-runtime
  image:
    kind: preset
    name: node24
  resources:
    memoryMB: 8192
  steps:
    - RUN apt-get update && apt-get install -y --no-install-recommends postgresql-client jq && rm -rf /var/lib/apt/lists/*
    - RUN npm install -g tsx
```

```yaml .auto/agents/staff-engineer.yaml theme={null}
name: staff-engineer
imports:
  - ../fragments/environments/agent-runtime-base.yaml
```

Identical inline environments that share a name deduplicate into one resource; two *different* definitions under the same name fail the apply loudly. See [fragments](/concepts/fragments) and [imports and fragments](/reference/imports-and-fragments) for the merge rules.

<Warning>
  When environments merge across imports, **arrays replace rather than concatenate**: an agent that re-declares `steps` overrides the imported list entirely instead of appending to it. A specialized runtime that needs "base plus more" must restate the base steps — treat it as a standalone environment, not an additive overlay.
</Warning>

## Where the code appears

Environments describe the machine; [mounts](/reference/mounts) describe the code on it. Each git mount clones a repository to its `mountPath`, and the agent's working directory defaults to the first mount's path (a relative `workingDirectory` resolves inside it and may not escape it). Setup commands run in that working directory — an environment with setup but no mounts has nowhere to run it, so setup is skipped.

<CardGroup cols={2}>
  <Card title="Environment reference" href="/reference/environments">
    Every field: image, steps, setup, cache, resources, approvals.
  </Card>

  <Card title="Mounts reference" href="/reference/mounts">
    Git mounts, auth capabilities, refs, and clone depth.
  </Card>

  <Card title="Secrets reference" href="/reference/secrets">
    The project secret store, \$secret references, and injection.
  </Card>

  <Card title="Sandbox runtime" href="/runtime/sandbox">
    What is running inside the sandbox once a session starts.
  </Card>
</CardGroup>
