> ## 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.

# Validate an Environment Image

> Test a preset or direct Docker/OCI base image, authored build steps, setup commands, and repository smoke checks locally before applying an agent.

Use this workflow before applying an agent whose inline environment changes its base image, build steps, or setup commands. It gives a repository agent a plain-Docker preflight for the current `debian-contract-v0` hosted runtime contract; it does not apply a resource or create a hosted sandbox.

## Choose the authored image

An inline environment accepts either an auto-managed preset or one direct Docker/OCI base-image reference:

<CodeGroup>
  ```yaml Preset theme={null}
  image:
    kind: preset
    name: node24
  ```

  ```yaml Direct base image theme={null}
  image:
    kind: base
    ref: debian:bookworm
  ```
</CodeGroup>

The direct form accepts one image-reference token. Whitespace, comments, continuations, and option-looking values are rejected. auto inserts the reference into a generated, single-stage `FROM` instruction; it does **not** import a repository Dockerfile or upload a Docker build context as environment configuration.

Mutable tags can drift: a later build of `debian:bookworm` can resolve to different bytes even when the `.auto` file did not change. auto currently preserves the authored reference rather than resolving and persisting its digest. Pin `ref` to a registry digest when reproducible rebuilds matter.

## Debian contract v0

A direct base image must satisfy every hard requirement below.

| Requirement                       | Local proof                                                                                                          |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Operating system and architecture | Linux on `amd64` (`uname -s` is `Linux`; `uname -m` is `x86_64`)                                                     |
| C library                         | The x86-64 glibc loader exists and GNU libc is 2.28 or newer                                                         |
| Shell                             | `/bin/sh` exists and can execute Docker `RUN` instructions                                                           |
| Build user                        | Image build instructions can run as root; auto emits `USER root` before its probe and bootstrap                      |
| Git                               | `git` is already present or can be installed through the Debian/Ubuntu package surface used by the current bootstrap |

The hosted build then runs in this order: base image, `USER root`, compatibility probe, auto's harness bootstrap, your `environment.steps`, workspace-user creation, git mount, and your `environment.setup` commands as the non-root workspace user. A base without `/bin/sh` can fail before the probe can print a classified line. The current bootstrap also uses `apt-get` to install its base tools, including `git`, so a nominally glibc-compatible non-Debian image is not enough.

## Run the local preflight

<Steps>
  <Step title="Copy the authored sequence">
    Read the agent and any imported environment fragments first. Record the resolved `image.ref`, copy each `environment.steps` entry in order, copy each `environment.setup[].commands` entry in order, and identify the repository's existing smoke commands. Do not write a compiler or translator for `.auto`; this is a reviewed transcription of the environment you are about to apply.
  </Step>

  <Step title="Create a disposable source export">
    Run from the repository root after committing the environment change you intend to apply. The quoted heredoc reads the image ref as data, so shell metacharacters in a reviewed file are never evaluated by the host shell. Replace only its one data line.

    ```sh theme={null}
    validation_root="$(mktemp -d "${TMPDIR:-/tmp}/auto-environment-validation.XXXXXX")"
    validation_context="$validation_root/context"
    validation_dockerfile="$validation_root/Dockerfile"
    validation_tag="auto-environment-validation:local"
    mkdir -p "$validation_context"
    git archive --format=tar HEAD | tar -xf - -C "$validation_context"

    IFS= read -r image_ref <<'AUTO_IMAGE_REF'
    debian:bookworm
    AUTO_IMAGE_REF
    ```

    The Docker context now contains only files committed at `HEAD`: no `.git` directory, ignored file, untracked `.env`, or untracked `.npmrc` can enter the image or Docker build cache. If the relevant changes are not committed, stop and commit them before continuing; the export deliberately ignores the live working tree.
  </Step>

  <Step title="Create a temporary validation Dockerfile">
    Create the file at `$validation_dockerfile`, using the template below. Keep the compatibility-probe line byte-for-byte intact. Replace only the marked authored step and setup examples with the commands from the repository.

    ```dockerfile Dockerfile theme={null}
    ARG AUTO_ENVIRONMENT_BASE=debian:bookworm
    FROM ${AUTO_ENVIRONMENT_BASE}
    USER root

    # environment-image-contract-probe:start
    RUN set -eu; auto_contract_fail() { echo "$1" >&2; exit 1; }; [ "$(id -u 2>/dev/null || true)" = "0" ] || auto_contract_fail 'AUTO_ENVIRONMENT_IMAGE_CONTRACT_ERROR contract=debian-contract-v0 check=root reason=requires-root-user'; [ "$(uname -s 2>/dev/null || true)" = "Linux" ] || auto_contract_fail 'AUTO_ENVIRONMENT_IMAGE_CONTRACT_ERROR contract=debian-contract-v0 check=os reason=requires-linux'; [ "$(uname -m 2>/dev/null || true)" = "x86_64" ] || auto_contract_fail 'AUTO_ENVIRONMENT_IMAGE_CONTRACT_ERROR contract=debian-contract-v0 check=arch reason=requires-linux-x86_64'; auto_glibc_loader=; for auto_glibc_candidate in /lib64/ld-linux-x86-64.so.2 /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2; do if [ -e "$auto_glibc_candidate" ]; then auto_glibc_loader=$auto_glibc_candidate; break; fi; done; [ -n "$auto_glibc_loader" ] || auto_contract_fail 'AUTO_ENVIRONMENT_IMAGE_CONTRACT_ERROR contract=debian-contract-v0 check=glibc-loader reason=requires-glibc-loader'; auto_glibc_version=$(getconf GNU_LIBC_VERSION 2>/dev/null || true); case "$auto_glibc_version" in "glibc "[0-9]*.[0-9]*) ;; *) auto_contract_fail 'AUTO_ENVIRONMENT_IMAGE_CONTRACT_ERROR contract=debian-contract-v0 check=glibc reason=requires-gnu-libc-2.28-or-newer' ;; esac; auto_glibc_version=${auto_glibc_version#glibc }; auto_glibc_major=${auto_glibc_version%%.*}; auto_glibc_minor=${auto_glibc_version#*.}; auto_glibc_minor=${auto_glibc_minor%%.*}; case "$auto_glibc_major:$auto_glibc_minor" in *[!0-9:]*|:*) auto_contract_fail 'AUTO_ENVIRONMENT_IMAGE_CONTRACT_ERROR contract=debian-contract-v0 check=glibc reason=requires-gnu-libc-2.28-or-newer' ;; esac; [ "$auto_glibc_major" -gt 2 ] || { [ "$auto_glibc_major" -eq 2 ] && [ "$auto_glibc_minor" -ge 28 ]; } || auto_contract_fail 'AUTO_ENVIRONMENT_IMAGE_CONTRACT_ERROR contract=debian-contract-v0 check=glibc reason=requires-gnu-libc-2.28-or-newer'; mkdir -p /opt/auto; : > /opt/auto/probe.env; if command -v git >/dev/null 2>&1; then auto_probe_available=1; else auto_probe_available=0; fi; echo "AUTO_PROBE_HAS_GIT=$auto_probe_available" >> /opt/auto/probe.env; if command -v bash >/dev/null 2>&1; then auto_probe_available=1; else auto_probe_available=0; fi; echo "AUTO_PROBE_HAS_BASH=$auto_probe_available" >> /opt/auto/probe.env; if command -v useradd >/dev/null 2>&1; then auto_probe_available=1; else auto_probe_available=0; fi; echo "AUTO_PROBE_HAS_USERADD=$auto_probe_available" >> /opt/auto/probe.env; if command -v apt-get >/dev/null 2>&1; then auto_probe_available=1; else auto_probe_available=0; fi; echo "AUTO_PROBE_HAS_APT_GET=$auto_probe_available" >> /opt/auto/probe.env;
    # environment-image-contract-probe:end

    # Equivalent compatibility bootstrap: proves the current Debian package path
    # can provide git and the workspace-user tooling.
    RUN apt-get update \
      && apt-get install -y --no-install-recommends git passwd \
      && rm -rf /var/lib/apt/lists/*
    RUN test -x /bin/sh && command -v git >/dev/null

    # Paste environment.steps here, unchanged and in order.
    RUN apt-get update \
      && apt-get install -y --no-install-recommends make \
      && rm -rf /var/lib/apt/lists/*

    # Local equivalent of auto's post-build workspace-user and mounted checkout.
    RUN set -eu \
      && if ! getent group user >/dev/null 2>&1; then groupadd user; fi \
      && if ! id -u user >/dev/null 2>&1; then useradd -m -s /bin/bash -g user user; fi \
      && mkdir -p /workspace/repository \
      && chown -R user:user /workspace/repository /home/user
    COPY --chown=user:user . /workspace/repository
    USER user
    WORKDIR /workspace/repository

    # Paste environment.setup[].commands here, unchanged and in order.
    RUN /bin/sh -lc 'set -eu; make environment-setup'
    ```

    The local `COPY` and final `RUN` model the hosted mount-then-setup order. They do not reproduce the auto harness packages or E2B's setup snapshot cache, so this remains a compatibility and repository-command preflight rather than proof of a hosted template build.
  </Step>

  <Step title="Build and run the known-good case">
    Ask Docker to parse and pull the ref first, then build and run both legs explicitly as `linux/amd64`. Docker's own reference parser rejects values outside canonical Docker/OCI reference grammar; a parse or pull failure stops the preflight. Keep the variable expansions quoted exactly as shown and replace the smoke command with the repository's real checks.

    ```sh theme={null}
    docker image pull --platform linux/amd64 -- "$image_ref"

    docker build \
      --platform linux/amd64 \
      --build-arg "AUTO_ENVIRONMENT_BASE=$image_ref" \
      --file "$validation_dockerfile" \
      --tag "$validation_tag" \
      "$validation_context"

    docker run \
      --rm \
      --platform linux/amd64 \
      "$validation_tag" \
      /bin/sh -lc 'set -eu; test "$(uname -m)" = x86_64; test "$(id -u)" != 0; git --version; make test'
    ```

    A pass proves the selected base can execute the compatibility path, the authored build and setup commands succeed in the right order and user context, and the repository's chosen smoke checks pass inside the resulting image.
  </Step>

  <Step title="Prove the incompatible classification">
    Run the same Dockerfile and clean export against Alpine. `alpine:3.23` is a deterministic negative fixture for this contract because it uses musl rather than glibc.

    ```sh theme={null}
    incompatible_ref="alpine:3.23"
    incompatible_log="$validation_root/incompatible.log"
    docker image pull --platform linux/amd64 -- "$incompatible_ref"

    if docker build \
      --platform linux/amd64 \
      --build-arg "AUTO_ENVIRONMENT_BASE=$incompatible_ref" \
      --file "$validation_dockerfile" \
      --tag auto-environment-validation:incompatible \
      "$validation_context" >"$incompatible_log" 2>&1; then
      echo "expected alpine:3.23 to fail debian-contract-v0" >&2
      exit 1
    fi

    grep -F \
      'AUTO_ENVIRONMENT_IMAGE_CONTRACT_ERROR contract=debian-contract-v0 check=glibc-loader reason=requires-glibc-loader' \
      "$incompatible_log"
    ```

    The marker is classified as an `image_build` failure: the image violates `debian-contract-v0`, so retrying it as provider infrastructure trouble would not help.
  </Step>

  <Step title="Clean up the local artifacts">
    ```sh theme={null}
    docker image rm --force "$validation_tag"
    case "$validation_root" in
      */auto-environment-validation.*) rm -rf -- "$validation_root" ;;
      *) echo "refusing to remove unexpected validation path: $validation_root" >&2; exit 1 ;;
    esac
    ```
  </Step>
</Steps>

## Author the inline environment

After the local preflight passes, put the environment directly on an agent or in an imported agent fragment. Standalone `.auto/environments/` files are invalid.

```yaml .auto/agents/repository-agent.yaml theme={null}
name: repository-agent
harness: codex
environment:
  name: repository-runtime
  image:
    kind: base
    ref: debian:bookworm
  steps:
    - RUN apt-get update && apt-get install -y --no-install-recommends make && rm -rf /var/lib/apt/lists/*
  setup:
    - name: repository-smoke
      commands:
        - make environment-setup
mounts:
  - kind: git
    repository: acme/widgets
    mountPath: /workspace/widgets
    ref: main
    auth:
      kind: githubApp
      capabilities:
        contents: read
workingDirectory: /workspace/widgets
```

Keep repository tests in their normal CI as well; the local image check adds a platform-compatibility leg but does not replace repository CI.

## Run the hosted preflight

In a hosted auto sandbox, validate the committed `.auto` working tree through the local Auto MCP tool before apply:

```text Focused agent file theme={null}
auto.resources.dry_run({
  "paths": [".auto/agents/repository-agent.yaml"]
})
```

Use `auto.resources.dry_run({})` when you want full-directory, prune-aware validation. Both forms are read-only: they parse, validate, and plan the resource set, but they do not apply it and do not execute a production E2B template build. A clean dry-run therefore proves the hosted authoring surface accepts the exact image shape; it does not replace the local Docker preflight or a later real session through the hosted provider.

<CardGroup cols={2}>
  <Card title="Environment reference" href="/reference/environments">
    Field constraints for images, build steps, setup, caches, resources, and approvals.
  </Card>

  <Card title="GitHub Sync" href="/concepts/github-sync">
    How committed `.auto` changes are validated and applied after merge.
  </Card>
</CardGroup>
