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

> Build Sailbox images with packages, local files, commands, and environment variables

Sailbox images define the root filesystem used to start a VM. Start from a
Debian base image, then chain build steps to install dependencies, copy local
files, run setup commands, and set environment variables.

<CodeGroup>
  ```python Python theme={null}
  import sail

  image = (
      sail.Image.debian_arm64
      .apt_install("git", "curl")
      .pip_install("requests")
      .add_local_dir("./app", "/opt/app", ignore=["*.pyc", "__pycache__/"])
      .env({"APP_ENV": "production"})
  )
  ```

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

  const image = Image.debian("arm64")
    .aptInstall("git", "curl")
    .pipInstall("requests")
    .addLocalDir("./app", "/opt/app", { ignore: ["*.pyc", "__pycache__/"] })
    .env({ APP_ENV: "production" });
  ```

  ```rust Rust theme={null}
  use std::collections::HashMap;

  use sail::{BaseImage, ImageArchitecture};
  use sail::imagebuild::{ImageDefinition, ImageDefinitionStep};

  let image = ImageDefinition {
      base: Some(BaseImage::Debian),
      architecture: ImageArchitecture::Arm64,
      env: HashMap::from([("APP_ENV".to_string(), "production".to_string())]),
      steps: vec![
          ImageDefinitionStep::AptInstall(vec!["git".into(), "curl".into()]),
          ImageDefinitionStep::PipInstall(vec!["requests".into()]),
          ImageDefinitionStep::AddLocalDir {
              local_path: "./app".into(),
              remote_path: "/opt/app".into(),
              ignore: vec!["*.pyc".into(), "__pycache__/".into()],
              ignore_file: None,
          },
      ],
      ..Default::default()
  };
  ```
</CodeGroup>

Image definitions are immutable values: each builder step gives you a new
definition, so you can safely reuse a base image across multiple variants.

## Base images

Use a Debian base image for the target architecture:

<CodeGroup>
  ```python Python theme={null}
  arm_image = sail.Image.debian_arm64
  amd_image = sail.Image.debian_amd64
  ```

  ```typescript TypeScript theme={null}
  const armImage = Image.debian("arm64");
  const amdImage = Image.debian("amd64");
  ```

  ```rust Rust theme={null}
  let arm_image = ImageDefinition {
      base: Some(BaseImage::Debian),
      architecture: ImageArchitecture::Arm64,
      ..Default::default()
  };
  let amd_image = ImageDefinition {
      base: Some(BaseImage::Debian),
      architecture: ImageArchitecture::Amd64,
      ..Default::default()
  };
  ```
</CodeGroup>

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

## Install Python packages

Install Python dependencies at build time:

<CodeGroup>
  ```python Python theme={null}
  image = sail.Image.debian_arm64.pip_install(
      "requests",
      "beautifulsoup4",
  )
  ```

  ```typescript TypeScript theme={null}
  const image = Image.debian("arm64").pipInstall("requests", "beautifulsoup4");
  ```

  ```rust Rust theme={null}
  let image = ImageDefinition {
      base: Some(BaseImage::Debian),
      architecture: ImageArchitecture::Arm64,
      steps: vec![ImageDefinitionStep::PipInstall(vec![
          "requests".into(),
          "beautifulsoup4".into(),
      ])],
      ..Default::default()
  };
  ```
</CodeGroup>

Package installation happens at image build time, before any Sailbox starts.
This is usually faster and more reproducible than installing packages in every
new VM with `exec()`.

## Add local files

Copy a single local file into the image:

<CodeGroup>
  ```python Python theme={null}
  image = sail.Image.debian_arm64.add_local_file(
      "./config.json",
      "/etc/demo/config.json",
      mode=0o600,
  )
  ```

  ```typescript TypeScript theme={null}
  const image = Image.debian("arm64").addLocalFile(
    "./config.json",
    "/etc/demo/config.json",
    { mode: 0o600 },
  );
  ```

  ```rust Rust theme={null}
  let image = ImageDefinition {
      base: Some(BaseImage::Debian),
      architecture: ImageArchitecture::Arm64,
      steps: vec![ImageDefinitionStep::AddLocalFile {
          local_path: "./config.json".into(),
          remote_path: "/etc/demo/config.json".into(),
          mode: Some(0o600),
      }],
      ..Default::default()
  };
  ```
</CodeGroup>

Or copy a directory tree:

<CodeGroup>
  ```python Python theme={null}
  image = sail.Image.debian_arm64.add_local_dir(
      "./app",
      "/opt/demo-app",
      ignore=["*.pyc", "__pycache__/"],
  )
  ```

  ```typescript TypeScript theme={null}
  const image = Image.debian("arm64").addLocalDir("./app", "/opt/demo-app", {
    ignore: ["*.pyc", "__pycache__/"],
  });
  ```

  ```rust Rust theme={null}
  let image = ImageDefinition {
      base: Some(BaseImage::Debian),
      architecture: ImageArchitecture::Arm64,
      steps: vec![ImageDefinitionStep::AddLocalDir {
          local_path: "./app".into(),
          remote_path: "/opt/demo-app".into(),
          ignore: vec!["*.pyc".into(), "__pycache__/".into()],
          ignore_file: None,
      }],
      ..Default::default()
  };
  ```
</CodeGroup>

Remote paths must be absolute POSIX paths. Directory uploads preserve file
modes from the local filesystem and skip symlinks. `ignore` accepts
gitignore-style patterns, or point at an existing ignore file (such as
`.gitignore`) instead.

Use image files for source code, static assets, and configuration that should
exist before boot. Use [Filesystem](/sailboxes-filesystem) for runtime inputs,
outputs, logs, and data that changes per Sailbox.

## Install system packages

Install Debian packages with apt:

<CodeGroup>
  ```python Python theme={null}
  image = sail.Image.debian_arm64.apt_install(
      "git",
      "curl",
      "openssh-server",
  )
  ```

  ```typescript TypeScript theme={null}
  const image = Image.debian("arm64").aptInstall("git", "curl", "openssh-server");
  ```

  ```rust Rust theme={null}
  let image = ImageDefinition {
      base: Some(BaseImage::Debian),
      architecture: ImageArchitecture::Arm64,
      steps: vec![ImageDefinitionStep::AptInstall(vec![
          "git".into(),
          "curl".into(),
          "openssh-server".into(),
      ])],
      ..Default::default()
  };
  ```
</CodeGroup>

## Run shell commands

Run shell commands during the image build:

<CodeGroup>
  ```python Python theme={null}
  image = (
      sail.Image.debian_arm64
      .apt_install("python3")
      .pip_install("requests")
      .run_commands("python3 -m pip show requests >/tmp/requests.txt")
  )
  ```

  ```typescript TypeScript theme={null}
  const image = Image.debian("arm64")
    .aptInstall("python3")
    .pipInstall("requests")
    .runCommand("python3 -m pip show requests >/tmp/requests.txt");
  ```

  ```rust Rust theme={null}
  let image = ImageDefinition {
      base: Some(BaseImage::Debian),
      architecture: ImageArchitecture::Arm64,
      steps: vec![
          ImageDefinitionStep::AptInstall(vec!["python3".into()]),
          ImageDefinitionStep::PipInstall(vec!["requests".into()]),
          ImageDefinitionStep::RunCommand(
              "python3 -m pip show requests >/tmp/requests.txt".to_string(),
          ),
      ],
      ..Default::default()
  };
  ```
</CodeGroup>

Build commands run once while the image is prepared. They do not run each time a
Sailbox starts.

## Set environment variables

Bake environment variables into Sailboxes created from the image:

<CodeGroup>
  ```python Python theme={null}
  image = sail.Image.debian_arm64.env(
      {
          "APP_ENV": "demo",
          "LOG_LEVEL": "info",
      }
  )
  ```

  ```typescript TypeScript theme={null}
  const image = Image.debian("arm64").env({
    APP_ENV: "demo",
    LOG_LEVEL: "info",
  });
  ```

  ```rust Rust theme={null}
  let image = ImageDefinition {
      base: Some(BaseImage::Debian),
      architecture: ImageArchitecture::Arm64,
      env: HashMap::from([
          ("APP_ENV".to_string(), "demo".to_string()),
          ("LOG_LEVEL".to_string(), "info".to_string()),
      ]),
      ..Default::default()
  };
  ```
</CodeGroup>

## Create a Sailbox from an image

Pass the image definition to `Sailbox.create()`:

<CodeGroup>
  ```python Python theme={null}
  app = sail.App.find(name="custom-image-demo", mint_if_missing=True)

  image = (
      sail.Image.debian_arm64
      .apt_install("git", "curl")
      .pip_install("requests")
      .add_local_dir("./app", "/opt/app", ignore=["*.pyc", "__pycache__/"])
      .env({"APP_ENV": "custom-image-demo"})
  )

  sb = sail.Sailbox.create(
      app=app,
      image=image,
      name="custom-image-demo",
      image_build_timeout=1800,
  )
  ```

  ```typescript TypeScript theme={null}
  const app = await App.find("custom-image-demo", { mintIfMissing: true });

  const image = Image.debian("arm64")
    .aptInstall("git", "curl")
    .pipInstall("requests")
    .addLocalDir("./app", "/opt/app", { ignore: ["*.pyc", "__pycache__/"] })
    .env({ APP_ENV: "custom-image-demo" });

  const sb = await Sailbox.create({
    app,
    name: "custom-image-demo",
    image,
    imageBuildTimeoutSeconds: 1800,
  });
  ```

  ```rust Rust theme={null}
  let image = ImageDefinition {
      base: Some(BaseImage::Debian),
      architecture: ImageArchitecture::Arm64,
      env: HashMap::from([("APP_ENV".to_string(), "custom-image-demo".to_string())]),
      steps: vec![
          ImageDefinitionStep::AptInstall(vec!["git".into(), "curl".into()]),
          ImageDefinitionStep::PipInstall(vec!["requests".into()]),
          ImageDefinitionStep::AddLocalDir {
              local_path: "./app".into(),
              remote_path: "/opt/app".into(),
              ignore: vec!["*.pyc".into(), "__pycache__/".into()],
              ignore_file: None,
          },
      ],
      ..Default::default()
  };

  let spec = client
      .build_image_definition(&image, Duration::from_secs(1800))
      .await?;

  let app = client.find_app("custom-image-demo", /* mint_if_missing */ true).await?;
  let handle = client
      .create_sailbox(
          &CreateSailboxRequest {
              app_id: app.id,
              name: "custom-image-demo".to_string(),
              image: spec,
              ..Default::default()
          },
          /* timeout */ None,
      )
      .await?;
  ```
</CodeGroup>

In Python and TypeScript, `Sailbox.create()` uploads any local files, builds
the image if it has not already been built, then starts the VM from that image.
In Rust, `build_image_definition` runs that same upload-and-build pipeline and
returns the built spec to create from.

## Build an image ahead of time

Build the image before creating a Sailbox:

<CodeGroup>
  ```python Python theme={null}
  image = (
      sail.Image.debian_arm64
      .apt_install("git")
      .pip_install("requests")
      .build(timeout=1800)
  )

  sb = sail.Sailbox.create(
      app=app,
      image=image,
      name="prebuilt-image-demo",
  )
  ```

  ```typescript TypeScript theme={null}
  const spec = await Image.debian("arm64")
    .aptInstall("git")
    .pipInstall("requests")
    .build({ timeoutSeconds: 1800 });

  const sb = await Sailbox.create({
    app,
    name: "prebuilt-image-demo",
    image: spec,
  });
  ```

  ```rust Rust theme={null}
  let image = ImageDefinition {
      base: Some(BaseImage::Debian),
      architecture: ImageArchitecture::Arm64,
      steps: vec![
          ImageDefinitionStep::AptInstall(vec!["git".into()]),
          ImageDefinitionStep::PipInstall(vec!["requests".into()]),
      ],
      ..Default::default()
  };

  let spec = client
      .build_image_definition(&image, Duration::from_secs(1800))
      .await?;

  let handle = client
      .create_sailbox(
          &CreateSailboxRequest {
              app_id: app_id.clone(),
              name: "prebuilt-image-demo".to_string(),
              image: spec,
              ..Default::default()
          },
          /* timeout */ None,
      )
      .await?;
  ```
</CodeGroup>

## Build-time boot and start snapshots

As the final stage of the build pipeline, Sail may boot your image once after
the build completes and capture a start snapshot. Sailboxes created from the
image can then resume from that snapshot instead of cold-booting, which makes
first starts as fast as later ones.

This is part of the image build contract:

* **Your image's first boot can happen at build time**, not when the first
  Sailbox is created. Boot-time initialization (systemd units, init scripts,
  services configured to start on boot) runs during that build-time boot.
* **State generated during the build-time boot may be shared.** Anything your
  boot process writes to disk or leaves in memory becomes part of the start
  snapshot that every Sailbox created from this image resumes from. Do not
  generate per-instance identity (machine IDs, cryptographic nonces, cached
  credentials) during boot and expect it to be unique per Sailbox.
* **Per-Sailbox identity is injected at create time.** Environment variables,
  networking, and Sail-managed credentials are applied when each Sailbox is
  created, after the snapshot resumes, so runtime configuration behaves the
  same whether or not a start snapshot was used.
* The build-time boot is best-effort: if snapshot capture is skipped or fails,
  the first Sailbox simply cold-boots exactly as it would have without one.

Generate anything that must be unique per Sailbox at runtime (for example in
your application entrypoint), not during image boot.

## Image caching

Sail caches image builds per organization by content. If the base image, build steps,
environment variables, and uploaded file contents are unchanged, later Sailboxes
can reuse the existing image instead of rebuilding it. A change to any build
step or local file content creates a new image.
