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 lets you run a local Python function inside a Sailbox 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 prebuilt 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 is prebuilt, and uv/mise lazy-install further language toolchains on demand. In exchange, they don’t accept build steps: 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. To get a Sailbox with SSH and coding agents set up in one command, use sail box devbox up. Devbox Sailboxes run systemd, so services work the way they do on any Debian machine. Docker starts at boot and keeps running across sleeps, and cron is not installed, so schedule recurring jobs with systemd timers. with_systemd describes how services, timers, and reboot behave in a Sailbox. A devbox created before systemd support keeps its original startup, including after upgrade. Create a new devbox to use systemd. 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. Because Sail pulls it and layers the Sailbox runtime on top, 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 a bare 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 determines what the tag means, regardless of which build finishes first. A digest identifies 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. The image’s 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. Because Sail builds it and layers the Sailbox runtime on top, you can chain every builder method onto 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. Its .dockerignore is honored, and ignore patterns are applied on top. As with Docker, a .dockerignore file named after your Dockerfile, such as Dockerfile.dockerignore, is used instead when present. Every image that a FROM or COPY --from references must be 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 that a FROM or COPY --from references 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 an image from a Dockerfile for the full behavior.

from_compose

Build a Docker Compose project into the image. The image contains Docker, the project’s service images, and a copy of the project. Create a Sailbox from it and call compose_up to start the project.
Pass the directory you would run docker compose in. files lists the compose files to build from, env gives values for their variables, and project_name names the project. The image builds for amd64 unless you pass architecture. See Build an image from a Compose project 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

Copies 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

Copies a local directory into the image at remote_path. Each regular file is hashed and uploaded with its local file mode. 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 in the image. Requires at least one non-empty key.

with_systemd

Runs systemd as the init system in Sailboxes created from the image. The build installs systemd before the image’s build steps run, wherever in the chain you call this, so a run_commands step can enable a service with systemctl enable. Enabled services start when a Sailbox is created from the image, and one may still be starting when create returns. Images do not run systemd unless you call this. The devbox base always runs it, so calling this on a devbox base changes nothing. An image built with from_compose cannot use systemd. See Run services with systemd for an example. Inside the Sailbox, start more services with systemctl enable --now, check them with systemctl status, and read their logs with journalctl. The journal is kept in memory. Running reboot or poweroff inside the Sailbox stops it, and Sail restarts it from its latest checkpoint. Changes to the Sailbox’s own disk since that checkpoint are lost; files on mounted volumes are kept. Schedule recurring jobs with systemd timer units. A timer set with OnCalendar= is a wall-clock alarm, so the Sailbox can sleep and Sail wakes it shortly before the timer is due (see Autosleep). A timer set with a relative interval such as OnUnitActiveSec= keeps the Sailbox awake, the same as a process waiting in a sleep loop.

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. A forced build that fails doesn’t change anything. For an image imported with from_registry through a tag, a forced build also looks up the tag in the registry again and builds the version it points at now. 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 in its FROM and COPY --from instructions 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 determines 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. The debian bases pin the local version for this reason.
  • 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 be 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.