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

# Images & Functions

> Base images, the custom-image builder, and running Python functions in a Sailbox

Images define the root filesystem a Sailbox boots from: start from a base
image, optionally chain build steps, and pass the result to
[`Sailbox.create`](/sailbox-sdk#sailbox-create). `@sail.function`
additionally lets Python ship a local function into a Sailbox and run it as if
it were local. See the [Images guide](/sailboxes-images) for a task-oriented
walkthrough.

## Base images

<CodeGroup>
  ```python Python theme={null}
  arm = sail.Image.debian_arm64
  amd = sail.Image.debian_amd64
  # Devtools preinstalled, for use as a remote developer workstation:
  dev = sail.Image.devbox_arm64
  ```

  ```typescript TypeScript theme={null}
  import { Image } from "@sailresearch/sdk";

  const arm = Image.debian("arm64");
  const amd = Image.debian("amd64");
  // Devtools preinstalled, for use as a remote developer workstation:
  const dev = Image.devbox("arm64");
  ```

  ```rust Rust theme={null}
  use sail::{BaseImage, ImageArchitecture};
  use sail::imagebuild::ImageDefinition;

  let arm = ImageDefinition {
      base: Some(BaseImage::Debian),
      architecture: ImageArchitecture::Arm64,
      ..Default::default()
  };
  ```
</CodeGroup>

The `devbox` images are the Debian base plus a baked development layer: Node
LTS with `npm`, `build-essential` compilers, the OS libraries editor remote
servers need, the `claude` and `codex` CLIs, and common developer tools
(`jq`, `gh`, `fd`, `fzf`, `uv`, `mise`, `tmux`, `git-lfs`, and more). They
boot fast because the whole layer ships prebuilt, and `uv`/`mise`
lazy-install further language toolchains on demand. The trade-off is that
they are prebuilt only: builder methods such as `apt_install` and
`pip_install` are rejected on a devbox base. Use a `debian` base when you
need custom build steps.

Devbox images also have a working clipboard. During `sail box shell`,
Ctrl+V puts your local clipboard on the guest's clipboard. Pasting a
screenshot into `claude` or `codex` works exactly as it does on your own
machine, and text copied inside the guest is copied back to your local
clipboard.

In Python, `sail.Image.debian_arm64` and `debian_amd64` (aliases
`debian_arm` / `debian_amd`) pin the image to your local Python version so
`@sail.function` can deserialize local bytecode.

## The image builder

An image definition is an immutable value. Builder methods return a new
definition, so you chain them and either pass the result straight to
`Sailbox.create` (which builds it for you) or call `build()` to build
eagerly.

<CodeGroup>
  ```python Python theme={null}
  image = (
      sail.Image.debian_arm64
      .apt_install("git", "curl")
      .pip_install("httpx")
      .env({"LOG_LEVEL": "info"})
      .build()
  )
  ```

  ```typescript TypeScript theme={null}
  const spec = await Image.debian("arm64")
    .aptInstall("git", "curl")
    .pipInstall("httpx")
    .env({ LOG_LEVEL: "info" })
    .build();
  ```

  ```rust Rust theme={null}
  use std::collections::HashMap;
  use sail::{BaseImage, ImageArchitecture};
  use sail::imagebuild::{ImageDefinition, ImageDefinitionStep};
  use std::time::Duration;

  let image = ImageDefinition {
      base: Some(BaseImage::Debian),
      architecture: ImageArchitecture::Arm64,
      env: HashMap::from([("LOG_LEVEL".to_string(), "info".to_string())]),
      steps: vec![
          ImageDefinitionStep::AptInstall(vec!["git".into(), "curl".into()]),
          ImageDefinitionStep::PipInstall(vec!["httpx".into()]),
      ],
      ..Default::default()
  };
  let spec = client
      .build_image_definition(&image, Duration::from_secs(1800))
      .await?;
  ```
</CodeGroup>

### apt\_install

<CodeGroup>
  ```python Python theme={null}
  def apt_install(*packages: str) -> ImageDefinition
  ```

  ```typescript TypeScript theme={null}
  aptInstall(...packages: string[]): Image
  ```

  ```rust Rust theme={null}
  ImageDefinitionStep::AptInstall(packages: Vec<String>)
  ```
</CodeGroup>

Adds a step that installs Debian packages with `apt`. Requires at least one
non-empty package name.

### pip\_install

<CodeGroup>
  ```python Python theme={null}
  def pip_install(*packages: str) -> ImageDefinition
  ```

  ```typescript TypeScript theme={null}
  pipInstall(...packages: string[]): Image
  ```

  ```rust Rust theme={null}
  ImageDefinitionStep::PipInstall(packages: Vec<String>)
  ```
</CodeGroup>

Adds a step that installs Python packages with `pip`. Requires at least one
non-empty package name.

### run\_commands

<CodeGroup>
  ```python Python theme={null}
  def run_commands(*cmd: str) -> ImageDefinition
  ```

  ```typescript TypeScript theme={null}
  runCommand(command: string): Image
  ```

  ```rust Rust theme={null}
  ImageDefinitionStep::RunCommand(command: String)
  ```
</CodeGroup>

Adds one build step per shell command, in order. Each command must be
non-empty.

### add\_local\_file

<CodeGroup>
  ```python Python theme={null}
  def add_local_file(
      local_path: str | Path,
      remote_path: str,
      *,
      mode: int | None = None,
  ) -> ImageDefinition
  ```

  ```typescript TypeScript theme={null}
  addLocalFile(localPath: string, remotePath: string, options?: {
    mode?: number;
  }): Image
  ```

  ```rust Rust theme={null}
  ImageDefinitionStep::AddLocalFile {
      local_path: PathBuf,
      remote_path: String,
      mode: Option<u32>,
  }
  ```
</CodeGroup>

Bakes the contents of one local file into the image at `remote_path`. Only
the file's content hash, target path, and mode identify the image, so a
one-byte change forces a rebuild.

| Parameter     | Default  | Description                                                              |
| ------------- | -------- | ------------------------------------------------------------------------ |
| `local_path`  | required | Path to the local file.                                                  |
| `remote_path` | required | Absolute POSIX destination. A trailing slash appends the local basename. |
| `mode`        | `None`   | POSIX permission bits (low 9 bits, max `0o777`). Defaults to `0o644`.    |

Raises an invalid-argument error if the source is missing, the path is
invalid, or the file exceeds the 5 GiB single-file limit.

### add\_local\_dir

<CodeGroup>
  ```python Python theme={null}
  def add_local_dir(
      local_path: str | Path,
      remote_path: str,
      *,
      ignore: Sequence[str] | Path | str | None = None,
  ) -> ImageDefinition
  ```

  ```typescript TypeScript theme={null}
  addLocalDir(localPath: string, remotePath: string, options?: {
    ignore?: string[];
    ignoreFile?: string;
  }): Image
  ```

  ```rust Rust theme={null}
  ImageDefinitionStep::AddLocalDir {
      local_path: PathBuf,
      remote_path: String,
      ignore: Vec<String>,
      ignore_file: Option<PathBuf>,
  }
  ```
</CodeGroup>

Bakes a local directory into the image at `remote_path`. Each regular file is
hashed and uploaded; per-file modes come from the local stat. Symlinks are
skipped. `ignore` takes gitignore-style patterns, or point at an existing
ignore file (such as `.gitignore`) instead. `remote_path` must be absolute.

### env

<CodeGroup>
  ```python Python theme={null}
  def env(env: dict[str, str]) -> ImageDefinition
  ```

  ```typescript TypeScript theme={null}
  env(env: Record<string, string>): Image
  ```

  ```rust Rust theme={null}
  // ImageDefinition field:
  env: HashMap<String, String>
  ```
</CodeGroup>

Sets environment variables baked into the image. Requires at least one
non-empty key.

### build

<CodeGroup>
  ```python Python theme={null}
  def build(*, timeout: int = 1800) -> ImageDefinition
  ```

  ```typescript TypeScript theme={null}
  build(options?: { timeoutSeconds?: number }): Promise<ImageSpec>
  ```

  ```rust Rust theme={null}
  pub async fn build_image_definition(
      &self, // Client
      def: &ImageDefinition,
      timeout: Duration,
  ) -> Result<ImageSpec, SailError>
  ```
</CodeGroup>

Builds the image and blocks until it is ready, returning a built definition
you can create Sailboxes from. `timeout` bounds the whole pipeline (local
file uploads and the build) and must be `> 0`.

Raises an image-build error if the build fails and a timeout error if it does
not finish within `timeout`.

<Note>
  You rarely need to call `build()` yourself: passing an unbuilt definition to
  [`Sailbox.create`](/sailbox-sdk#sailbox-create) builds it first (bounded by
  `image_build_timeout`).
</Note>

***

<h2 id="sail-function">
  @sail.function
</h2>

Python only.

```python theme={null}
@sail.function
def fn(...): ...
# or
@sail.function()
def fn(...): ...
```

Decorates a Python function so it can run inside a Sailbox via
[`Sailbox.exec`](/sailbox-sdk#exec). The decorator returns a
[`SailFunction`](#sailfunction); calling it locally still invokes the original
function unchanged.

```python theme={null}
@sail.function
def add(x: int, y: int) -> int:
    return x + y

value = sb.exec(add, 2, 3, timeout=30)
print(value)  # 5
```

When you pass a `SailFunction` to `exec`, the call blocks and returns the
function's return value directly (not a `ExecProcess`). The SDK
serializes the function plus its arguments, runs it with the image's
`python3`, and returns the deserialized result.

**Constraints:**

* Function execution is synchronous; `background=True` is not supported.
* The Sailbox's `python3` must match your local Python major.minor, because
  the serialized bytecode is version-sensitive. This is why the `debian`
  bases pin the local version.
* Imported third-party packages are referenced by name, so they must exist in
  the Sailbox environment.
* Keep arguments and return values small; write large artifacts from inside
  the Sailbox and return a small reference instead.

**Raises** `sail.SailboxFunctionError` (with the remote `error_type`,
`traceback`, `stdout`, `stderr` attached) when the function raises remotely,
and `sail.SailboxFunctionSerializationError` if the payload or result cannot
be serialized or the runtime cannot be prepared. See
[Errors](/sailbox-sdk-errors).

<h3 id="sailfunction">
  SailFunction
</h3>

The wrapper returned by `@sail.function`. You normally don't construct it
directly. Calling a `SailFunction` locally is identical to calling the
wrapped function. Async functions, async generators, and generator functions
are rejected at decoration time with `TypeError`.

| Member                      | Description                           |
| --------------------------- | ------------------------------------- |
| `func`                      | The wrapped callable.                 |
| `__call__(*args, **kwargs)` | Invokes the wrapped function locally. |
