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
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.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.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 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; 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
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, 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.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. This is why thedebianbases 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 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 stayautofor 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.