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
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.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.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 callcompose_up to start the project.
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 toSailbox.create (which builds it for you) or call build() to build
eagerly.
apt_install
apt. Requires at least one
non-empty package name.
pip_install
pip. Requires at least one
non-empty package name.
run_commands
add_local_file
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
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
with_systemd
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
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.Sailbox.exec. The decorator returns a
SailFunction. Calling it locally still invokes the original
function unchanged.
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=Trueis not supported. - The Sailbox’s
python3must match your local Python major.minor, because the serialized bytecode is version-sensitive. Thedebianbases 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 raisessail.SailboxFunctionSerializationError. A second call with the sameidempotency_keywhile the function runs takes over its output, and the earlier call may then fail to decode its result.output_modemust beautofor a function.
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.