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

# Sandbox Images

> Define reusable named sandbox images in Python, TypeScript, or Dockerfiles.

Sandbox images let you set up dependencies, files, and environment once, then launch fresh sandboxes from that prepared state.

Define an image with a Dockerfile, the Python SDK, or the TypeScript SDK — or import an existing registry image directly — then pass the registered name to `image=` when creating sandboxes.

The usual flow is:

1. Choose a base image.
2. Define the setup steps with a Dockerfile or `Image` object.
3. Build and register the image name in your project.
4. Create sandboxes from that registered name.

## Base Images

Tensorlake ships preconfigured base images that boot quickly and are tuned for common sandbox workloads:

* `tensorlake/ubuntu-minimal` (*default sandbox image*): Minimal Ubuntu without systemd. Use this when you want the fastest cold starts.
* `tensorlake/ubuntu-systemd`: Ubuntu with systemd. Use this when you need services such as Docker or Kubernetes inside the sandbox.
* `tensorlake/debian-minimal`: Minimal Debian 13.

In environments where desktop automation is enabled, you may also see:

* `tensorlake/ubuntu-vnc`: Desktop-enabled Ubuntu based on `tensorlake/ubuntu-systemd`, with XFCE, TigerVNC, and Firefox preinstalled. Use it for browser automation and computer-use workloads. See [Computer Use](/sandboxes/computer-use).

## Build and Register an Image

You can define the same image with a Dockerfile, Python, or TypeScript. The build runs the setup steps and registers the result under the image name in your project.

<Tabs>
  <Tab title="CLI">
    ```dockerfile Dockerfile theme={null}
    FROM tensorlake/ubuntu-systemd

    RUN apt-get update && apt-get install -y python3 python3-pip
    COPY requirements.txt /tmp/requirements.txt
    RUN python3 -m pip install --break-system-packages -r /tmp/requirements.txt
    RUN mkdir -p /workspace/cache

    ENV APP_ENV=prod
    WORKDIR /workspace
    ```

    ```bash theme={null}
    tl sbx image create ./Dockerfile --registered-name data-tools-image
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python theme={null}
    from tensorlake import Image

    image = (
        Image(name="data-tools-image", base_image="tensorlake/ubuntu-systemd")
        .copy("requirements.txt", "/tmp/requirements.txt")
        .run("apt-get update && apt-get install -y python3 python3-pip")
        .run("python3 -m pip install --break-system-packages -r /tmp/requirements.txt")
        .run("mkdir -p /workspace/cache")
        .env("APP_ENV", "prod")
        .workdir("/workspace")
    )

    image.build(registered_name="data-tools-image", context_dir=".")
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    import { Image } from "tensorlake";

    const image = new Image({
      name: "data-tools-image",
      baseImage: "tensorlake/ubuntu-systemd",
    })
      .copy("requirements.txt", "/tmp/requirements.txt")
      .run("apt-get update && apt-get install -y python3 python3-pip")
      .run("python3 -m pip install --break-system-packages -r /tmp/requirements.txt")
      .run("mkdir -p /workspace/cache")
      .env("APP_ENV", "prod")
      .workdir("/workspace");

    await image.build({
      registeredName: "data-tools-image",
      contextDir: ".",
    });
    ```
  </Tab>
</Tabs>

In the SDKs, `context_dir` (`contextDir` in TypeScript) is optional and works like the build context in `docker build <context>`. Pass it when the `Image` reads host files — through `copy()`, `add()`, or a `RUN --mount=type=bind` — so those sources resolve relative to it. Omit it otherwise.

### Build from an OCI Base

The build base can be any standard OCI image reference, not just `tensorlake/*`, for example `python:3.12-slim`, `debian:bookworm-slim`, `node:22-alpine`, `ghcr.io/...`, or `public.ecr.aws/...`.

```dockerfile Dockerfile theme={null}
FROM python:3.12-slim

RUN apt-get update && apt-get install -y curl
RUN python3 -m pip install pandas pyarrow duckdb
WORKDIR /workspace
```

```bash theme={null}
tl sbx image create ./Dockerfile --registered-name py-data-tools
```

The first build from a new OCI base takes longer because the upstream image has to be fetched and prepared. Subsequent builds are faster.

### Private Registries

If you can `docker pull` an image from a private registry, you can use it as a base or dependency in your sandbox image's Dockerfile. Authenticate with `docker login`, then run the build:

```bash theme={null}
docker login ghcr.io
tl sbx image create ./Dockerfile --registered-name my-private-image
```

`docker login` works with all private registries, including Docker Hub, GHCR, ECR, GCR, Quay, and self-hosted. During the build, the Tensorlake CLI and SDKs read registry credentials from `~/.docker/config.json` (or `$DOCKER_CONFIG/config.json` if `DOCKER_CONFIG` is set) and use them to pull private base images and dependencies. If the credentials are missing or expired, the build fails when it tries to pull from the private registry.

This also works in CI. For example, if you authenticate to ECR with [amazon-ecr-login](https://github.com/aws-actions/amazon-ecr-login) in a GitHub Actions workflow, `tl sbx image create` and SDK calls in the same workflow pick up those credentials.

## Import an Image from a Registry

To use an existing registry image as a sandbox image without adding any build steps, import it directly. There is no Dockerfile and no build context, and the reference is always pulled fresh from the registry.

Use this when you want a published image (`ubuntu:24.04`, `pytorch/pytorch:2.4.1-cuda12.1-cudnn9-runtime`, `ghcr.io/org/app:v1`) as-is. If you need to layer extra packages, files, or environment on top, write a Dockerfile that uses it as a `FROM` base instead — see [Build from an OCI Base](#build-from-an-oci-base).

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    tl sbx image import pytorch/pytorch:2.4.1-cuda12.1-cudnn9-runtime \
      --registered-name pytorch-runtime
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python theme={null}
    from tensorlake import import_sandbox_image

    import_sandbox_image(
        "pytorch/pytorch:2.4.1-cuda12.1-cudnn9-runtime",
        registered_name="pytorch-runtime",
    )
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    import { importSandboxImage } from "tensorlake";

    await importSandboxImage(
      "pytorch/pytorch:2.4.1-cuda12.1-cudnn9-runtime",
      { registeredName: "pytorch-runtime" },
    );
    ```
  </Tab>
</Tabs>

If you omit the registered name, it defaults to the reference's last path segment with any tag or digest stripped (`pytorch/pytorch:2.4.1` → `pytorch`, `ghcr.io/org/app@sha256:...` → `app`).

Imports use the same `docker login` credentials as Dockerfile builds, so private references work the same way (see [Private Registries](#private-registries)). The same CPU, memory, disk, and visibility options apply as for builds (see [Build Resources](#build-resources) and [Public Images](#public-images)).

## Launch Sandboxes from an Image

Create a sandbox from the registered image name. You can still override CPU, memory, disk, timeout, and entrypoint when the sandbox starts.

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    tl sbx create --image data-tools-image
    ```

    ```bash theme={null}
    tl sbx create \
      --image data-tools-image \
      --cpus 4.0 \
      --memory 4096 \
      --disk_mb 51200 \
      --timeout 1800
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from tensorlake.sandbox import Sandbox

    sandbox = Sandbox.create(
        image="data-tools-image",
        cpus=4.0,
        memory_mb=4096,
        disk_mb=51200,
        timeout_secs=1800,
    )

    try:
        result = sandbox.run(
            "python3",
            ["-c", "import pandas, pyarrow; print('ready')"],
        )
        print(result.stdout)
    finally:
        sandbox.terminate()
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    import { Sandbox } from "tensorlake";

    const sandbox = await Sandbox.create({
      image: "data-tools-image",
      cpus: 4.0,
      memoryMb: 4096,
      diskMb: 51200,
      timeoutSecs: 1800,
    });

    try {
      const result = await sandbox.run("python3", {
        args: ["-c", "import pandas, pyarrow; print('ready')"],
      });

      console.log(result.stdout);
    } finally {
      await sandbox.terminate();
    }
    ```
  </Tab>
</Tabs>

<Note>
  You can't launch a sandbox directly from a Docker/registry image reference — it has to be registered as a Tensorlake image first. The quickest way to do that for an unmodified image is [Import an Image from a Registry](#import-an-image-from-a-registry), which registers it in one step with no Dockerfile. We are working on launching public registry images directly without a separate registration step.
</Note>

## Python Packages

The Tensorlake Ubuntu and Debian base images ship a PEP 668-managed system Python, so `pip install` requires `--break-system-packages` unless you create a virtual environment. Without it, `pip` exits with `error: externally-managed-environment`.

For one-off installs in a running sandbox:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    sandbox.run(
        "python3",
        ["-m", "pip", "install", "--break-system-packages", "pandas", "pyarrow", "duckdb"],
    )
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    await sandbox.run("python3", {
      args: ["-m", "pip", "install", "--break-system-packages", "pandas", "pyarrow", "duckdb"],
    });
    ```
  </Tab>
</Tabs>

For repeatable installs, put the packages in `requirements.txt` and install them during the image build, as shown in [Build and Register an Image](#build-and-register-an-image).

<Warning>
  Do not sidestep PEP 668 by switching Python versions. `python3.11 -m pip install ...` or another alternate system Python can produce the same `externally-managed-environment` error. Use `--break-system-packages` with the system `python3`, or create an explicit virtual environment.
</Warning>

## Build Resources

Builds run in a temporary builder sandbox. You can allocate more CPU, memory, or disk for the builder, and separately set the root disk size of the resulting image.

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    tl sbx image create ./Dockerfile \
      --registered-name data-tools-image \
      --cpus 4 \
      --memory 4096 \
      --disk_mb 25600 \
      --builder_disk_mb 32768
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python theme={null}
    image.build(
        registered_name="data-tools-image",
        cpus=4.0,
        memory_mb=4096,
        disk_mb=25600,
        builder_disk_mb=32768,
    )
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    await image.build({
      registeredName: "data-tools-image",
      cpus: 4.0,
      memoryMb: 4096,
      diskMb: 25600,
      builderDiskMb: 32768,
    });
    ```
  </Tab>
</Tabs>

`disk_mb` / `diskMb` sets the root disk size for sandboxes created from the registered image. `builder_disk_mb` / `builderDiskMb` only affects the temporary builder sandbox.

Build defaults are `cpus=2.0`, `memory=4096 MB`, and a generated root disk of `10240 MiB` (10 GiB).

### Docker Compatibility Mode

`--docker_compat` runs the build or import with standard Docker/BuildKit instead of Tensorlake's default builder. Turn it on if a build or import fails or produces an unexpected result under the default builder, it trades speed and disk for maximum compatibility. Budget at least 3× the builder disk and memory (via the resource flags above). The flag works on both builds and imports; leave it off unless you need it.

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    tl sbx image create ./Dockerfile \
      --registered-name data-tools-image \
      --docker_compat
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python theme={null}
    image.build(registered_name="data-tools-image", docker_compat=True)
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    await image.build({
      registeredName: "data-tools-image",
      dockerCompat: true,
    });
    ```
  </Tab>
</Tabs>

## Register an Existing Snapshot as an Image

If you already have a completed filesystem snapshot, you can give it a reusable image name without rebuilding:

```bash theme={null}
tl sbx image register data-tools-image snap_01HX... \
  --dockerfile ./Dockerfile
```

The first positional argument is the image name to register, the second is the completed snapshot ID, and `--dockerfile` is stored alongside the image so `tl sbx image describe` can show how it was built. Add `--public` to make the name resolvable from any namespace (see [Public Images](#public-images)).

The snapshot must be in `Completed` status with a durable `snapshot_uri`; `tl sbx image register` rejects snapshots that haven't finished uploading.

## Inspect and List Registered Images

List the images registered in your project, or look one up by name, from the CLI or the SDKs.

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    tl sbx image ls                       # list every image registered in the current project
    tl sbx image describe data-tools-image # show Dockerfile, snapshot ID, image size
    ```

    `describe` accepts either the registered image name or the underlying sandbox-template ID.
  </Tab>

  <Tab title="Python SDK">
    ```python theme={null}
    from tensorlake import find_sandbox_image_by_name, list_sandbox_images

    images = list_sandbox_images()  # every image registered in the current project

    image = find_sandbox_image_by_name("data-tools-image")  # None if no such image exists
    if image is not None:
        print(image["id"], image["snapshot_id"])
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    import { findSandboxImageByName, listSandboxImages } from "tensorlake";

    const images = await listSandboxImages(); // every image registered in the current project

    const image = await findSandboxImageByName("data-tools-image"); // null if no such image exists
    if (image) {
      console.log(image.id, image.snapshotId);
    }
    ```
  </Tab>
</Tabs>

The SDK list and lookup calls use the same environment-based Tensorlake auth as image builds, and require organization and project context (`TENSORLAKE_ORGANIZATION_ID` and `TENSORLAKE_PROJECT_ID`).

## Public Images

By default a registered image is namespace-scoped. Pass `--public`, `is_public=True`, or `isPublic: true` to make the image name resolvable from any namespace. This is how the `tensorlake/*` base images work.

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    tl sbx image create ./Dockerfile --registered-name shared-base --public
    ```
  </Tab>

  <Tab title="Python SDK">
    ```python theme={null}
    image.build(registered_name="shared-base", is_public=True)
    ```
  </Tab>

  <Tab title="TypeScript SDK">
    ```typescript theme={null}
    await image.build({
      registeredName: "shared-base",
      isPublic: true,
    });
    ```
  </Tab>
</Tabs>

Public image names must be globally unique for the registry. Names that collide with an already-registered public image will be rejected at creation time.

## Examples

### Skills Image

This variant preloads the [Tensorlake skills repo](/agent-skills) so coding agents can auto-discover it at startup:

```dockerfile Dockerfile theme={null}
FROM tensorlake/ubuntu-systemd

RUN apt-get update && apt-get install -y git nodejs npm python3 python3-pip
RUN npm install -g skills
RUN skills add tensorlakeai/tensorlake-skills --all -y --copy
RUN python3 -m pip install --break-system-packages tensorlake
```

If the file is named `Dockerfile`, the registered name defaults to the parent directory name. Otherwise it defaults to the file stem. Registered image names must be unique within a project.

## Supported Build Operations and Limitations

Sandbox image builds support most of the standard Dockerfile commands and features, but with some limitations:

* Dockerfile `$VAR` and environment variable substitution is not working in `FROM` commands
* Dockerfile `ONBUILD` commands are ignored and do not run during child image builds
* The following Dockerfile commands work as expected during image builds but do not have any effect when running sandboxes from the images:
  * `ONBUILD`
  * `SHELL`
  * `EXPOSE`
  * `HEALTHCHECK`
  * `LABEL`
  * `STOPSIGNAL`
  * `VOLUME`

## See Also

<CardGroup cols={3}>
  <Card title="Snapshots" icon="camera" href="/sandboxes/snapshots">
    Understand the underlying snapshot primitive used to save and restore sandbox state.
  </Card>

  <Card title="Lifecycle" icon="arrows-spin" href="/sandboxes/lifecycle">
    Learn which sandbox settings you can still override when launching from an image.
  </Card>

  <Card title="Skills in Sandboxes" icon="wand-magic-sparkles" href="/sandboxes/skills-in-sandboxes">
    Ship Tensorlake SDK docs inside sandbox images for agents and tools.
  </Card>
</CardGroup>
