Skip to main content
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. @sail.function additionally lets Python ship a local function into a Sailbox and run it as if it were local. See the Images guide for a task-oriented walkthrough.

Base images

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, Docker with the docker compose and docker buildx plugins, and common developer tools (jq, gh, fd, fzf, uv, mise, tmux, git-lfs, and more). Devbox images 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. The Docker daemon starts automatically when a devbox Sailbox boots and keeps running across sleeps. Right after a fresh boot, the daemon can take a few seconds to accept commands. If it ever stops, start dockerd again as root. If it complains about a stale pid file, delete /var/run/docker.pid and retry. 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 are shorthand for sail.Image.debian("arm64") and debian("amd64"), and pin the image to your local Python version so @sail.function can deserialize local bytecode. Pass install_python=False, as in sail.Image.debian("arm64", install_python=False), to keep the base’s stock python3 instead.

from_registry

Use your own image as the root filesystem. Sail pulls it and layers the Sailbox runtime on top, so every builder method works the same as on a Debian base.
Reference a Debian- or Ubuntu-based image on a supported public registry (docker.io, ghcr.io, public.ecr.aws, or quay.io), written as you would for docker pull: python:3.13 means docker.io/library/python:3.13, acme/tool means docker.io/acme/tool, and the other registries are named in full, as in ghcr.io/acme/tool. You can pass a tag, a digest (name@sha256:...), or just the name, which means the latest tag. A tag is pinned for your organization once an image has been built from it: later builds keep getting that version, even if the tag moves upstream. Use force_build (forceBuild in TypeScript, BuildMode::ForceBuild in Rust) to look the tag up again and move the pin for your whole organization. If forced builds of the same tag overlap, the last-requested one that succeeds decides what the tag means, no matter which build finishes first. A digest names exactly one image, so it never moves. Your Sailbox runs on the CPU architecture the image was built for; an image published for both amd64 and arm64 runs on amd64. Pass architecture to require one, and the build fails if the image was not built for it. Its environment variables, working directory, and USER become the defaults for commands you run; its ENTRYPOINT and CMD are not run. See Bring your own base image for the full requirements.

from_dockerfile

Build your own Dockerfile into the image. Sail builds it and layers the Sailbox runtime on top, so every builder method composes on the result.
Pass the path to a Dockerfile, or its literal text with contents= ({ contents } in TypeScript, DockerfileInput::Contents in Rust). context_dir is the directory COPY and ADD read from, with .dockerignore honored (a .dockerignore file named after your Dockerfile, for example Dockerfile.dockerignore, is used instead when present, as with Docker) and ignore patterns applied on top. Every image a FROM or COPY --from names must live on a supported public registry, and a short name works as you would expect: FROM python:3.12 means docker.io/library/python:3.12. The image the Dockerfile produces must be Debian- or Ubuntu-based. The build runs for amd64 unless you pass architecture, and build_args values fill the Dockerfile’s ARG instructions. Tags a FROM or COPY --from names are pinned on your organization’s first use and reused after that; force_build (forceBuild in TypeScript, BuildMode::ForceBuild in Rust) looks them up again. See Build one from a Dockerfile for the full behavior.

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.

apt_install

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

pip_install

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

run_commands

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

add_local_file

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

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

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

build

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. Everything you create from the result needs no further build. By default, Sail may reuse an existing ready build for the definition (BuildMode::ReuseExisting in Rust). Use force_build=True, forceBuild: true, or BuildMode::ForceBuild to build it again: new Sailboxes use the fresh image once it is ready, Sailboxes that already exist keep the filesystem they were created with, and a forced build that fails changes nothing. For an image imported with from_registry through a tag, a forced build also asks the registry what the tag points at now and builds that version. The tag then means that version for your whole organization, while the result of an earlier build keeps its pinned version. For an image built with from_dockerfile, a forced build looks up the tags its FROM and COPY --from instructions name and moves those pins for your whole organization, while the result of an earlier build keeps the versions its build used. If forced builds overlap, the last-requested one that succeeds decides which image new Sailboxes use and, for a tag, what the tag means. Raises an image-build error if the build fails and a timeout error if it does not finish within timeout.
You rarely need to call build() yourself: passing an unbuilt definition to Sailbox.create builds it first (bounded by image_build_timeout).

@sail.function

Python only.
Decorates a Python function so it can run inside a Sailbox via Sailbox.exec. The decorator returns a SailFunction; calling it locally still invokes the original function unchanged.
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. The function’s complete encoded response (its serialized return value, captured stdout and stderr, and any error details, as encoded on the wire) must fit the exec’s output_buffer_bytes (1 MiB by default, up to 64 MiB); a larger response raises sail.SailboxFunctionSerializationError. A second call with the same idempotency_key while the function runs takes over its output, and the earlier call may then fail to decode its result. output_mode must stay auto for a function.
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.

SailFunction

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.