Skip to main content
The Sail Python SDK (sail on PyPI) supports Python 3.9+. Sail also provides TypeScript and Rust SDKs.

Install

Installing the Python SDK also puts the sail CLI on your PATH. To install the CLI on its own, see Install the CLI. The Sail API warns when your SDK version is nearing the end of its support window. The SDK emits it as a SailDeprecationWarning through Python’s warnings module, once per process. A version past the end of its support window is rejected with an upgrade error before any operation runs. Upgrade with pip install -U sail.

Configure

Set SAIL_API_KEY in the environment; the SDK also reads the credential sail auth login stores under ~/.sail. See Configuration.

Quickstart

Sync and async

Every method that does I/O has an async twin under .aio (the interactive shell is sync-only), so the same code works from scripts and from asyncio. You choose sync or async once, at the call. A handle returned by an .aio call is already async (await proc.wait(), async for chunk in proc.stdout), with no further .aio:
See Sailbox → Sync and async for streaming and end-to-end examples.

Python-only features

Errors

Product and transport failures derive from sail.SailError, and the error classes that match a Python builtin also inherit it (sail.NotFoundError is a LookupError), so both except sail.SailError and idiomatic builtin handlers work. A few argument mistakes raise plain ValueError/TypeError. See Errors.

Reference

The docs below are auto-generated.

Sailbox

A sandbox instance on the Sail platform.sailbox_id is the stable identifier: every operation addresses the Sailbox by it, and two objects for the same Sailbox compare equal regardless of when they were fetched. Every other attribute is a snapshot of what get or list last returned. A lifecycle call on this object, such as pause, updates status, and set_auto_sleep and set_egress_policy update their attribute. Nothing else changes until the next get. The object create and from_checkpoint return starts with only the id, name, and status. Sail wakes a sleeping Sailbox only when an operation needs it.Attributes:

create

Create a new Sailbox.
Custom image definitions are built first; the call then returns once the new Sailbox is running or creation has failed.ingress_ports exposes guest ports for ingress. Each entry is either a bare int (shorthand for an HTTP port) or an IngressPort carrying an explicit protocol, e.g. ingress_ports=[80, 443, IngressPort(22, "tcp")]. Call listener / listeners on the returned Sailbox for the public address of each exposed port: an HTTP listener’s endpoint is an HttpEndpoint with a routable url and a TCP listener’s is a TcpEndpoint with a host/port any TCP client can dial (for example psql -h <host> -p <port>).SSH is enabled after create with enable_ssh, which trusts your org’s SSH certificate authority, starts sshd, and exposes guest port 22 as tcp.visibility chooses who may operate the Sailbox, fixed for its life. "org" (the default) lets any credential in your org exec, copy files, SSH, or run lifecycle operations on it. "private" restricts all of that to you. An org admin can override that with a recorded reason for exec, files, setting a wake time, and the pause, sleep, resume, terminate, and upgrade operations. SSH, exposing or removing listeners, checkpoint, and restore stay creator-only. "private" requires an API key minted by your user (not a service key).volumes mounts shared persistent storage into the guest. Pass a mapping from absolute guest mount path to a sail.Volume returned by sail.Volume.find(), e.g. volumes={"/mnt/shared": volume}. For a read-only mount, use volumes={"/mnt/shared": {"volume": volume, "read_only": True}}. read_only defaults to False and is fixed for the Sailbox’s life. Writes through a read-only mount fail with EROFS, even as root. See the volume guide. Volumes are currently in Alpha. To pilot them, reach out in the Sail Slack: https://join.slack.com/t/sailresearchcrew/shared_invite/zt-41pdcym9j-UU0Ey~A~r6n2H0DQVQsQHQ.max_lifetime_seconds optionally limits this Sailbox’s wall-clock lifetime from creation, including sleep and pause. Sail permanently terminates it after expiration, even if this client disconnects. Omit for unlimited lifetime.size selects the resource size: "s", "m" (the default), or "l". Each size sets the vCPU count plus default memory and disk. Ongoing billing is based on observed usage, so a bigger size does not reserve CPU, memory, or disk. Each size has a separate one-time creation charge. Choose "s" when you want the fastest cold starts and resumes. Its lower ceilings also cap what a runaway workload can consume, so you don’t accidentally use more than you need. memory_limit_gib and disk_limit_gib tune that size’s default memory and disk ceilings in whole GiB, within its range. Omit them to keep the size’s own ceilings.image is the image to boot; omit it for the prebuilt Debian base (no image build). A custom image is built at create if it is not already cached; image_build_timeout (seconds) bounds that build.Sail may sleep a fully idle Sailbox; it wakes transparently on traffic or the next operation. auto_sleep turns that off or replaces the default idle window; see AutoSleep.egress_policy sets what the Sailbox may connect to and what happens to the HTTPS requests it sends: a saved sail.EgressPolicy (or its id), or a policy document such as sail.EgressPolicy.allow_only("pypi.org") or a dict. A document that references a secret must be saved first. Without a policy the Sailbox can reach any host. no_network() cannot be combined with ingress_ports. The policy can be changed later with set_egress_policy. See the egress policy guide.await Sailbox.create.aio(...) is the async form, building the image and provisioning the VM without blocking the event loop.

list

List the Sailboxes for the current org that match the filters, fetching pages until every match (or limit of them) is collected. Use list_page to page through results manually instead.
Filters are server-side. app_id filters by the owning app: a sail.App value or an app id string (resolve an app name through App.find first if needed). order sorts the results: "newest_active" returns the most recently active first (the default the server applies), "newest_created" the newest-created first. limit caps the total returned, bounding the fetch for large orgs; None returns every match.

list_page

List one page of Sailboxes alongside the pagination envelope (limit/offset/total/has_more).
Takes the same filters as list, plus limit and offset to select the page. order sorts the results: "newest_active" returns the most recently active first (the default the server applies), "newest_created" the newest-created first.

get

Fetch a Sailbox by id: the operable handle plus a fresh snapshot.
Validates access first: wrong-org ids and unknown ids both surface as LookupError (the server returns 404 for both to avoid leaking ownership across orgs). Nothing wakes here; operations resume a paused or sleeping Sailbox on demand. Call get again for a fresh snapshot.

from_id

Bind a handle to an existing Sailbox id without a network call.
The returned handle carries no snapshot fields (its name and status are empty), just the operable surface. The id is not verified to exist: operations on an unknown or inaccessible id fail with NotFoundError. Use get to validate the id and fetch a fresh snapshot instead.

from_checkpoint

Create a new running Sailbox from a durable checkpoint handle.
The new Sailbox uses the checkpoint’s writable disk and cleaned memory state, so background processes continue and the new Sailbox runs independently of the source. Commands started with exec stop, though their writes up to the checkpoint remain. Host-specific identity and network routes are removed before the checkpoint handle becomes ready. A Sailbox with volume mounts cannot create a reusable checkpoint. The new Sailbox keeps the original’s egress policy and auto-sleep setting.name names the new Sailbox. max_lifetime_seconds optionally limits the new Sailbox’s wall-clock lifetime from creation, including sleep and pause. Omit for unlimited. The source Sailbox’s deadline is not inherited.

terminate

Permanently terminate this Sailbox.
Idempotent: terminating a Sailbox that is already terminated succeeds, so cleanup paths can call it unconditionally.

pause

Checkpoint and pause this Sailbox until it is explicitly resumed.

sleep

Checkpoint and sleep this Sailbox until traffic or a wake restores it.
wake_at, when given, schedules a wall-clock wake before the sleep starts and returns the effective wake time: the sooner of this request and any wake already scheduled. If the Sailbox is sleeping when that moment arrives, Sail restores it. The wake can fire a little after the time you set, so treat it as approximate. A naive wake_at is interpreted as local time. Calling sleep on an already-sleeping Sailbox succeeds and just updates the scheduled wake.

set_auto_sleep

Replace when Sail may sleep this Sailbox on its own.
Each call replaces the whole setting: switching to AutoSleep.never clears any minimum wait set earlier, and switching back does not restore it.

checkpoint

Create a durable checkpoint handle for this Sailbox.
Running Sailboxes are snapshotted first. Paused and sleeping Sailboxes reuse their existing checkpoint. The call returns after Sail has prepared the clean start state that new Sailboxes use. Sailboxes with volume mounts are not supported. Upgrade a Sailbox that uses an older guest payload before you create a checkpoint handle.name sets a display name for the handle. ttl_seconds, when set, must be positive and overrides the server’s default retention window; use it to keep a checkpoint you intend to reuse as a template alive longer than the default. The returned handle’s expires_at reports when the checkpoint expires; starting a Sailbox from it after that fails.

upgrade

Upgrade this Sailbox’s runtime to the latest version.
Upgrading picks up new Sailbox features, fixes, and performance improvements without recreating the Sailbox. A running Sailbox reboots in place on its current disk: all filesystem state is preserved, but processes restart as they would after a machine reboot (data not yet written to disk is lost). Upgrading a paused or sleeping Sailbox does not wake it: the upgrade is recorded and applies automatically at the next wake.Returns an UpgradeResult: applied is True when nothing is left to apply, either because the Sailbox took the upgrade just now or because it was already current, and False when the upgrade is recorded for the next wake.

resume

Resume this paused or sleeping Sailbox, returning it to running.

listener

Fetch one listener by guest port without waking the Sailbox.

listeners

List this Sailbox’s listeners without waking it.

wait_for_listener

Block until the listener on guest_port is reachable end to end.
For an HTTP listener this probes the url, so a successful return means your guest HTTP server answered. For a TCP listener it opens a connection through the ingress edge and treats it as ready once the guest sends bytes (e.g. an SSH banner) or holds the connection open. This is a connectivity check, not an application-level health check. Raises TimeoutError if the listener does not become reachable within timeout seconds; float("inf") waits indefinitely.

expose

Expose an additional ingress port on this Sailbox at runtime.
protocol is "http" (a routable URL, the default) or "tcp" (a public host/port for raw TCP: ssh, Postgres, etc.). allowlist restricts which sources may connect: an entry that reads as an address or a range (e.g. ["203.0.113.0/24"]) matches source IPs, and every other entry is a Sail app name (app names on "http" listeners only; "tcp" allowlists must be addresses or ranges). An address must not carry an IPv6 zone, such as fe80::1%eth0, which names an interface on one machine rather than a source. Re-exposing a port under the same protocol sets its allowlist to what you pass, so pass the whole list every time; passing none clears the restriction and reopens the port. A raw-TCP port reclaims its previous address while your org still holds it idle; if another of your org’s Sailboxes took the address over, a new one is allocated, so read the endpoint from the returned Listener. Changing an exposed port’s protocol is rejected: unexpose an HTTP port and re-expose it, or use a different guest port for a raw-TCP one. Returns the Listener (its route_status is "unknown": the expose response does not report reachability).This works on a paused or sleeping Sailbox without waking it; a later resume serves the new listener. Probing reachability with wait_for_listener needs a running, connected Sailbox, so wait only once the Sailbox is running.

unexpose

Stop serving an exposed ingress port on this Sailbox.
A "tcp" port stops counting against your org’s raw-TCP quota once removed, but its public host/port stays owned by your org: another of your org’s Sailboxes may reuse the idle address, and it is never given to a different org. Re-expose-ing the same guest port reclaims the exact address while your org still holds it idle; after a reuse you get a new one. An "http" port carries no such reservation and is removed outright. Removing a port that is not exposed raises a LookupError.

set_egress_policy

Replace this Sailbox’s egress policy and return the new one.
Accepts what create accepts as egress_policy: a saved sail.EgressPolicy (or its id), or a policy document such as sail.EgressPolicy.allow_only("pypi.org") or a dict. The new policy applies to connections opened after the call. The returned policy is stored on this handle as egress_policy. no_network() is refused with sail.ApiError while the Sailbox exposes ports or SSH:

clear_egress_policy

Remove this Sailbox’s egress policy and return the new one.
The Sailbox can then reach any host and no rules apply: the empty policy {}. The same as set_egress_policy with sail.EgressPolicy.allow_all(). The returned policy is stored on this handle as egress_policy:

ingress_auth_headers

Fetch the ingress-identity headers for this Sailbox via the API.
Attach the returned headers to HTTP requests so they authenticate as this Sailbox against another listener whose allowlist contains this Sailbox’s app name, useful for host-side orchestrators and tests that drive Sailboxes from outside. Requires an organization-scoped API key and a live (non-terminated) Sailbox.Inside a Sailbox guest, prefer the module-level sail.ingress_auth_headers, which reads the same values from the guest environment without an API call.

enable_ssh

Make this Sailbox reachable over SSH, returning its endpoint.
Installs your org’s SSH certificate authority as trusted, (re)starts sshd, and exposes guest port 22 as tcp ingress once the CA-only daemon verifiably owns it (a failed enable never leaves port 22 newly exposed). Works on any running Sailbox and is the only way to enable SSH: create the Sailbox, then call this. Safe to re-run: the Sailbox’s host key is generated once and never rotated, so a caller’s known_hosts stays valid; re-run it to bring sshd back up if the (unsupervised) daemon stops. sshd survives sleep and checkpoint→resume.allowlist restricts which source addresses or ranges may connect to port 22, replacing any existing restriction. Left empty, a first enable opens the port to any source, and a re-enable leaves an existing restriction unchanged. Disabling SSH (sail box ssh disable) unexposes port 22 together with its restriction, so a later enable is a first enable.Anyone in the org connects with a short-lived certificate signed for their key, rather than installed keys (a private Sailbox is the exception, accepting only its creator’s certificates). The sail box ssh CLI fetches that certificate and writes the local SSH config; this method only prepares the Sailbox. By default it blocks until the port-22 listener is reachable and returns its TcpEndpoint; pass wait=False to skip the readiness probe and return None.

fs

Filesystem operations on this Sailbox’s guest: read and write files (buffered or streaming), and directory helpers.

run

Run a command to completion and return its buffered result.
A one-shot convenience over exec followed by wait(). A str runs via /bin/sh -lc; a sequence is exec’d directly. env adds environment variables for the command; cwd sets the working directory (string commands only, like exec); user picks the guest user the command runs as (see exec). The result’s stdout and stderr hold only the most recent output_buffer_bytes of each stream (1 MiB by default, up to 64 MiB), with stdout_truncated and stderr_truncated set when older output was dropped; the command never pauses for unread output. To get every byte, use exec and read the stream (see ExecProcess).check=True raises sail.CommandFailedError (carrying the completed result as result) when the command exits nonzero or times out.When timeout (seconds) elapses, the command is killed and run returns an ExecResult with timed_out=True, raising only when check=True.idempotency_key deduplicates retries: calling run again with the same key returns the original command’s result instead of launching it a second time. While the first call is still running, a second call with the same key takes over its output stream, and the earlier call’s result may come back truncated. The UTF-8 value can be up to 256 KiB.

compose_up

Start the Docker Compose project this Sailbox’s image was built from.
The image, from Image.from_compose, holds a copy of the project at /root/<its directory name> and the image Sail built for each service. compose_up runs docker compose up there. Calling it again on a running project restarts the services whose definition changed.env sets environment variables for Compose, such as a secret kept out of the project’s .env. A variable in env wins over the same one in .env, as a shell variable does locally.Returns once every service is running, and healthy where it declares a healthcheck. A start that fails, or outlasts timeout (unlimited by default), raises sail.CommandFailedError with Compose’s output.

exec

Run a shell command or decorated Python function in the Sailbox.
For shell commands, returns a ExecProcess immediately after Sail accepts the command. By default a stream you are reading pauses the command when you fall behind, so nothing is lost until a cancel or the exec timeout ends the pauses, and a stream you are not reading keeps only its most recent 1 MiB. Accessing proc.stdout or proc.stderr is what starts reading, so access it right after this call returns when you need every byte (see ExecProcess). output_mode changes that: "pipe" holds both streams until you read them, so a late reader still gets every byte, and "tail" never pauses the command for you (see OutputMode). output_buffer_bytes sets each stream’s buffer size, from 64 KiB to 64 MiB; it is what wait() returns per stream and how far a reader can fall behind before the command pauses. open_stdin=True opens the command’s stdin for proc.stdin writes; by default stdin is /dev/null so stdin-reading commands see immediate EOF instead of blocking.pty=True runs the command under a pseudo-terminal: isatty() is true, control bytes written to proc.stdin become signals (Ctrl-C is b"\x03"), and proc.resize(cols, rows) adjusts the window. stdout and stderr merge onto proc.output (proc.stderr stays empty). pty implies open_stdin. For a full interactive shell that drives the local terminal, use shell instead.env adds environment variables for the command. Entries override the guest defaults (including LANG and the IS_SANDBOX=1 sandbox marker) and the image environment. A few reserved variables that identify the Sailbox (such as SAILBOX_ID) cannot be overridden. For pty execs the local terminal environment (COLORTERM, LANG, LC_*, TERM_PROGRAM) is forwarded automatically for keys not set here.cwd runs the command in the directory you name (string commands only). Without it, commands start in the image’s working directory, or / when the image does not set one.user runs the command as that guest user: a user name, a numeric uid, or either with a group appended after a colon ("alice", 1000, "alice:staff", "1000:100", the Docker USER syntax). A named user must exist in the Sailbox’s /etc/passwd; a numeric uid need not. HOME (and USER/LOGNAME when a name resolves) default to the resolved account, with env entries still winning. When user is not given, commands run as the image’s USER if the image sets one, root otherwise; pass user="0:0" to force root ("root" is a user name like any other, resolved through the Sailbox’s /etc/passwd). Sailboxes created before user support shipped must call upgrade once first; until then such execs fail rather than run as root. The exact spelling user="0:0" needs no upgrade.idempotency_key deduplicates the launch, so a retry with the same key attaches to the same command instead of starting a new one. An exec has one live handle at a time: a second handle started with the same key takes over the stream, and the first stops receiving live output and resolves from a bounded recorded result. A first handle reconnecting after a dropped connection can race a handle that attached meanwhile, and either handle’s result may come back incomplete; avoid overlapping same-key handles. The UTF-8 value can be up to 256 KiB.background=True launches the command through a detached shell that returns immediately. Its output is discarded, so proc.stdout / proc.stderr stay empty and proc.wait() only confirms the launcher started it.await sb.exec.aio(...) is the async form: a shell command resolves to an AsyncExecProcess, a function to its return value. For a Python function, output_mode must stay "auto", and 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 output_buffer_bytes; 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.

shell

Open an interactive pty session on the Sailbox, driving the local terminal.
With no command, runs an interactive login shell. Pass command to run that under a pty instead (e.g. a REPL or vim). Either way the session is bridged to the local terminal: raw-mode keystrokes (including Ctrl-C, Ctrl-Z, and Ctrl-D) reach the remote process, its output renders locally, and terminal resizes propagate. Blocks until the remote process exits and returns its exit code. Requires an interactive local terminal (stdin and stdout must be TTYs) on a Unix machine.A dropped connection does not end the session. The shell prints a notice, keeps reconnecting, and redraws the screen once the Sailbox is reachable again. If the Sailbox goes to sleep, the next key pressed wakes it and resumes the same session. If it is paused, the shell says so when a key is pressed and sends the keys once the Sailbox is resumed. Pressing Ctrl-D while disconnected or paused leaves the session. The session ends if the Sailbox is terminated.This is the equivalent of ssh-ing into the Sailbox, without a separate SSH server. shell overrides the login shell (default $SHELL or /bin/bash); it is ignored when command is given.The session runs as the image’s USER when the image sets one, root otherwise: the same identity exec uses. user runs it as someone else instead (a user name or numeric uid, optionally with a group after a colon, like "alice", 1000, "alice:staff"); user="0:0" is always root. A user other than "0:0" requires a Sailbox whose guest honors requested users; on older Sailboxes the session fails until upgrade is called. env adds environment variables to the session, with the same precedence and reserved names as for exec.While attached, several local conveniences are forwarded: the Sailbox’s browser opens and localhost servers reach your machine, files dragged onto the terminal upload into the Sailbox and paste as guest paths, and Ctrl+V forwards your clipboard. On devbox images the clipboard is two-way: pasted images and text land on the Sailbox’s clipboard, and text copied inside the Sailbox comes back to yours. Other images upload a pasted image as a file and paste its path instead. Pass no_forward=True to turn all of it off, for example for an untrusted or automated session.

App

A Sail application.Attributes:

find

Find an app by name, optionally creating it if it doesn’t exist.

list

Return every app the current org owns, newest first.
Apps with no Sailboxes yet are included. The response is not paginated; the per-org app count is small.

ImageDefinition

apt_install

Add an apt-get install step for packages.

pip_install

Add a pip install step for packages.

run_commands

Add shell commands to the build, each as its own step.

add_local_file

Bake the contents of one local file into the image at remote_path.
The local file is hashed (sha256) and uploaded to Sail’s content-addressed asset store; only the hash, target path, and mode flow into the image spec. A one-byte change to the local file therefore changes the resulting image_id and forces a rebuild.remote_path must be an absolute POSIX path. If it ends with a slash, the basename of local_path is appended. mode is the POSIX permission bits (low 9 bits, max 0o777). When omitted (None) or 0 the default 0o644 applies; an explicit mode=0 is treated the same as omitting the argument.

add_local_dir

Bake a local directory into the image at remote_path.
Each regular file under local_path is hashed and uploaded; per-file modes come from the local stat(). Symlinks are skipped. ignore accepts a sequence of gitignore patterns or a Path to a file containing them (e.g. .dockerignore); pass a list to use patterns directly. remote_path must be an absolute POSIX path.

env

Bake environment variables into the image.

build

Build the image now and return the built definition.
Submits the spec to Sail and polls until the build is ready or fails, raising TimeoutError if the build does not finish within timeout seconds. Any automatic retries are included in that timeout.Creating Sailboxes from the returned definition needs no further build. For an image imported with Image.from_registry through a tag, it is also pinned to the exact version the build resolved the tag to, even if the tag later moves upstream.By default, Sail may reuse an existing ready build for this definition. Pass force_build=True 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 through a registry 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 definitions built earlier keep their pinned version. A forced build of an image built with Image.from_dockerfile or Image.from_compose also looks up the tags in its FROM and COPY --from instructions, or in its services’ image entries, and moves those pins for your whole organization, while definitions built earlier keep the versions their 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.

ImageNamespace

Base images a Sailbox can build on.Access these through the module-level sail.Image singleton, for example sail.Image.debian_arm64 or sail.Image.devbox_arm64. See the Images guide for how to choose between the Debian and devbox bases and the CPU architectures.

debian

Debian base for the given CPU architecture (default amd64).
By default the image gets a python3 matching your local Python version, which is what lets @sail.function run local Python functions inside a Sailbox. Pass install_python=False to keep the base’s stock python3 instead; a base with no Python install and no other build steps is prebuilt, so creating a Sailbox from it needs no build.Image.debian_amd64 and Image.debian_arm64 are shorthand for debian("amd64") and debian("arm64").

debian_amd64

Debian base for x86-64; shorthand for debian("amd64").

debian_arm64

Debian base for arm64; shorthand for debian("arm64").

devbox

Devbox base for the given CPU architecture (default amd64): Debian plus a baked development toolchain.
Docker is included, and its daemon starts automatically when the Sailbox boots and keeps running across sleeps. The daemon can take a few seconds to accept commands right after boot. If it stops, it is not restarted automatically.The devbox base is prebuilt only: build steps and env are not supported on it, so start from debian to customize. Image.devbox_amd64 and Image.devbox_arm64 are shorthand for devbox("amd64") and devbox("arm64").

devbox_amd64

Devbox base for x86-64; shorthand for devbox("amd64").

devbox_arm64

Devbox base for arm64; shorthand for devbox("arm64").

from_registry

Your own image as the Sailbox root filesystem.
Sail pulls the image and layers everything a Sailbox needs on top, so the result behaves like any other image: build steps, env, and Sailbox.create all work the same. The image keeps its own python3, which pip_install and @sail.function use. Unlike debian, an imported image never gets a Python matching your local interpreter: installing one would shadow the Python the image was built around. An image without Python still gets one from the packages Sail installs. If you use @sail.function, an optional feature of the Python SDK that runs local Python functions inside a Sailbox, the image’s Python must match your local Python’s major.minor version.Reference an 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 and acme/tool means docker.io/acme/tool; name the registry for the others, as in ghcr.io/acme/tool. You can pass a tag, a @sha256:... digest, or just the name, which means the latest tag. The image must be Debian- or Ubuntu-based.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 instead, and building fails if the image was not built for it.A tag is pinned for your organization once an image has been built from it: later builds keep using that image even after the tag moves upstream. Call build(force_build=True) to look the tag up again and build the version it points at now for your whole organization; see build for how the switch propagates. A digest names exactly one image, so it never moves.The image’s environment variables, working directory, and USER become the defaults for commands you run with Sailbox.exec or Sailbox.run; per-call env, cwd, and user override them (pass user="0:0" to run as root on an image that sets USER). The image’s ENTRYPOINT and CMD are not run: a Sailbox manages its own processes, and your commands say what to execute. Build steps you chain onto the image (such as apt_install) and SSH sessions still run as root.

from_dockerfile

Build a Dockerfile into a Sailbox image.
Pass the path to a Dockerfile as the positional argument, or its literal text with contents=.context_dir is the build context that COPY and ADD read from. A .dockerignore file in the context is honored, and ignore patterns are applied on top of it. A file named after your Dockerfile, like Dockerfile.dockerignore, sitting next to it is used instead of the context’s .dockerignore, as it is with Docker. The files the ignore rules keep are hashed and uploaded when from_dockerfile is called, and their modes, empty directories, and symbolic links are carried into the build. Edits made after the call do not reach the build; call from_dockerfile again to pick them up. Without context_dir the build runs with an empty context.Every image a FROM or COPY --from instruction names must live on a supported public registry (docker.io, ghcr.io, public.ecr.aws, or quay.io). A short name like python:3.12 means docker.io/library/python:3.12. The Dockerfile must produce a Debian- or Ubuntu-based filesystem. Sail layers everything a Sailbox needs on top, so build steps, env, and Sailbox.create all work the same as any other image.A # syntax= line can declare docker/dockerfile:1 or a release from 1.4 through 1.22.0. A file that declares anything else is rejected. The declared release does not change how the file is built.The image builds for amd64 unless architecture says otherwise. build_args provides values for the Dockerfile’s ARG instructions. Names may not start with the reserved BUILDKIT_ prefix, and Docker’s proxy names (HTTP_PROXY, HTTPS_PROXY, FTP_PROXY, NO_PROXY, ALL_PROXY, in any letter case) are rejected; a step that needs a proxy can set one inside its RUN command.Multi-stage Dockerfiles work, and target names the stage to stop at, like docker build --target. A RUN --mount of type cache, secret, or ssh is rejected; tmpfs mounts work, and bind mounts work when they read from the build context or another build stage. Mount options must be literal text, and ONBUILD is not supported, in the Dockerfile or in an image a FROM names.Each image a FROM or COPY --from names is pinned to the version its tag pointed at the first time your organization used it, so rebuilding the same Dockerfile keeps using those versions even after a tag moves. To look every tag up again and build with the versions they point at now, pass force_build=True to build.The image’s environment variables, working directory, and USER become the defaults for commands you run with Sailbox.exec or Sailbox.run; per-call env, cwd, and user override them (pass user="0:0" to run as root on an image that sets USER), and values set with .env() win over the image’s. The image’s ENTRYPOINT and CMD are not run: a Sailbox manages its own processes, and your commands say what to execute. Build steps you chain onto the image (such as apt_install) and SSH sessions still run as root. The image keeps its own python3; if you use @sail.function, that Python must match your local Python’s major.minor version.

from_compose

Build a Docker Compose project into a Sailbox image.
project_dir is the directory you would run docker compose in. It is read when from_compose is called; edits after that do not reach the build. The image has Docker, every service image the project needs, and a copy of the directory at /root/<its directory name>, minus what its root .dockerignore excludes. Sailbox.compose_up starts the project from that copy. The project’s own .env, name:, and COMPOSE_FILE apply, as they do locally. The image builds for amd64 unless architecture says otherwise.Every service needs a build inside project_dir or an image to pull from a supported public registry (docker.io, ghcr.io, public.ecr.aws, or quay.io). A pulled image’s tag is pinned for your organization the first time a build uses it, like a Dockerfile FROM; force_build=True on ImageDefinition.build looks the tags up again. See the images guide for the compose features Sail supports.

ExecProcess

A command running in a Sailbox.Returned by Sailbox.exec. Each stream has a buffer, 1 MiB by default (output_buffer_bytes), and output_mode says what happens when it fills. With the default "auto": if you are not reading a stream, the command never pauses and the stream keeps only its most recent bytes; if you are reading a stream and fall behind, the command pauses when the buffer fills and resumes as you read, like a pipe. Reading a stream is how you get every byte, and it slows the command when you cannot keep up. "pipe" holds both streams from the start, so a reader that starts late still gets every byte; "tail" never pauses the command for you. See sail.OutputMode.With "auto", start reading right after exec() returns to get every byte. You can read stdout without holding stderr, or the reverse; the stream you are not holding keeps its most recent bytes and never pauses the command when it fills. If you hold both, read them at the same time, each from its own thread. The exit code is available from exit_code once the streams end and from wait().Accessing proc.stdout or proc.stderr (stdout_bytes / stderr_bytes for raw bytes) claims the stream and returns a generator. The stream is released when the generator ends, when you call close() on it, or when nothing references it any more (a for loop drops it when the loop ends, including by break); to stop early on purpose, keep the generator in a variable and call close(). Each stream can be claimed once; a second access raises sail.InvalidArgumentError.wait() returns each stream’s buffer, its most recent output, with stdout_truncated / stderr_truncated set when older output was dropped. close(), or your process exiting, releases both streams; the command keeps running, and wait() raises sail.InvalidArgumentError after close() unless it already resolved a result. Sail may reattach after an interruption, but reattachment does not guarantee exact output replay. A pty command never pauses; resync() requests a fresh screen.

exec_request_id

The durable identifier of this exec: the launch’s idempotency key as Sail recorded it (yours, or the one Sail generated when you did not supply one; read it here to learn the generated value). Reading it marks a generated identity as shareable: a second handle started with it takes over the stream, so from then on this handle no longer reclaims the stream after any interruption, a clean end or a dropped connection alike, and resolves from the recorded result instead. Reading back a key you supplied changes nothing.

stdout

Live stdout as a generator of str chunks (incrementally decoded UTF-8). Accessing this property claims the stream, so access it right after exec() returns when you need every byte; the generator releases the stream when it ends, when you call close() on it, or when nothing references it any more (see ExecProcess). Use stdout_bytes instead for raw bytes; a second access of either raises sail.InvalidArgumentError.

stderr

Live stderr generator; same as stdout, including that accessing it claims the stream. Empty for a pty exec, which merges stderr onto stdout.

output

Live merged terminal output for a pty exec (alias of stdout).
output and stdout are the same one-consumer stream.

stdout_bytes

Live stdout as a generator of raw bytes chunks, exactly as the command wrote them (escape sequences and binary payloads included). The same one-consumer stream as stdout and output: accessing it claims the stream the same way, and close() on the generator releases the stream early.

stderr_bytes

Raw bytes twin of stderr. Choose either accessor; this stream can be claimed once, and close() on the generator releases it.

output_bytes

Raw bytes twin of output (alias of stdout_bytes).

stdin

Stdin writer for an open_stdin=True exec; raises sail.InvalidArgumentError otherwise.

exit_code

Exit code once the streams end, else None.
Never blocks and never drops output. If the connection was lost for good mid-command, the streams end early with the outcome still unknown: this stays None and wait() fetches the result Sail recorded. A host-lost exec (the machine running the Sailbox was lost mid-command) has no real exit code: wait() always raises SailboxHostLostError for one, and this raises it when that loss is what ended the streams.

poll

Alias of exit_code; never blocks.

cancel

Signal the guest command: SIGINT by default, SIGKILL if force=True.
Idempotent on the server. Transient failures are retried briefly, covering the window right after the command starts when the guest cannot accept signals for it yet.

resize

Set the pty window (cols x rows) for a pty=True exec.
Advisory and best-effort: an unknown, finished, or not-yet-placed exec is a server no-op, and transient transport errors are swallowed since the next resize resends. A no-op for a non-pty exec.

resync

Ask a pty=True exec to repaint its current screen.
A command runs at full speed and never waits for a slow reader, so if you fall far behind the oldest output is dropped. Call this after that happens to receive the current screen instead of a broken, partial one. Advisory and best-effort; a no-op for a non-pty exec.

close

Abandon the handle without killing the command.
It releases both streams. The command keeps running and never pauses, and Sail keeps only the most recent output of each stream. Call cancel() instead if the command should stop. wait() raises sail.InvalidArgumentError after close() unless it already resolved a result.

wait

Wait for the exec to complete and return its result.
result.stdout and result.stderr hold each stream’s buffer, its most recent output (1 MiB by default), with stdout_truncated and stderr_truncated set when older output was dropped; to get every byte, read the stream (see ExecProcess). wait() itself never pauses the command and may run while a generator is still open. With output_mode="pipe", a stream nobody reads pauses the command when its buffer fills, and wait() then waits for as long as the command stays paused. After close() it raises sail.InvalidArgumentError unless a result was already resolved; a repeat wait() returns the cached result.Ctrl-C sends SIGINT and resumes waiting (the guest’s natural 128+SIGINT=130 exit code flows back); a second Ctrl-C escalates to SIGKILL and re-raises so a wedged guest can’t trap the caller.If stop is given and fires before the stream ends, wait() returns None and leaves the exec running, so a caller that no longer needs the result can return promptly. Without stop the result is non-None.

AsyncExecProcess

A command running in a Sailbox, with an async interface.Returned by await Sailbox.exec.aio(...). A chunk can be a partial line. Each stream has a buffer, 1 MiB by default (output_buffer_bytes), and output_mode says what happens when it fills. With the default "auto": if you are not reading a stream, the command never pauses and the stream keeps only its most recent bytes; if you are reading a stream and fall behind, the command pauses when the buffer fills and resumes as you read, like a pipe. Reading a stream is how you get every byte, and it slows the command when you cannot keep up. "pipe" holds both streams from the start, so a reader that starts late still gets every byte; "tail" never pauses the command for you. See sail.OutputMode.With "auto", start reading right after exec() returns, before awaiting anything else, to get every byte. You can read stdout without holding stderr, or the reverse; the stream you are not holding keeps its most recent bytes and never pauses the command when it fills. If you hold both, read them at the same time, each from its own task (asyncio.gather). The exit code is available from exit_code once the streams end and from wait().Accessing proc.stdout or proc.stderr (stdout_bytes / stderr_bytes for raw bytes) claims the stream and returns an async generator. The stream is released when the generator ends, when you await its aclose(), or when nothing references it any more (an async for loop drops it when the loop ends, including by break); to stop early on purpose, keep the generator in a variable and await its aclose(). Each stream can be claimed once; a second access raises sail.InvalidArgumentError.wait() returns each stream’s buffer, its most recent output, with stdout_truncated / stderr_truncated set when older output was dropped. close(), or your process exiting, releases both streams; the command keeps running, and wait() raises sail.InvalidArgumentError after close() unless it already resolved a result. Sail may reattach after an interruption, but reattachment does not guarantee exact output replay. A pty command never pauses; resync() requests a fresh screen.Cancelling the task awaiting wait() stops waiting but leaves the command running; call await proc.cancel() to signal the command itself.

exec_request_id

The durable identifier of this exec: the launch’s idempotency key as Sail recorded it (yours, or the one Sail generated when you did not supply one; read it here to learn the generated value). Reading it marks a generated identity as shareable: a second handle started with it takes over the stream, so from then on this handle no longer reclaims the stream after any interruption, a clean end or a dropped connection alike, and resolves from the recorded result instead. Reading back a key you supplied changes nothing.

stdout

Live stdout as an async generator of str chunks (incrementally decoded UTF-8). Accessing this property claims the stream, so access it right after exec() returns when you need every byte; the generator releases the stream when it ends, when you await its aclose(), or when nothing references it any more (see AsyncExecProcess). Use stdout_bytes instead for raw bytes; a second access of either raises sail.InvalidArgumentError.

stderr

Live stderr async generator; same as stdout, including that accessing it claims the stream. Empty for a pty exec, which merges stderr onto stdout.

output

Live merged terminal output for a pty exec (alias of stdout).
output and stdout are the same one-consumer stream.

stdout_bytes

Live stdout as an async generator of raw bytes chunks, exactly as the command wrote them. The same one-consumer stream as stdout and output: accessing it claims the stream the same way; await the generator’s aclose() to release the stream early.

stderr_bytes

Raw bytes twin of stderr. Choose either accessor; this stream can be claimed once, and aclose() on the generator releases it.

output_bytes

Raw bytes twin of output (alias of stdout_bytes).

stdin

Stdin writer for an open_stdin=True exec; raises sail.InvalidArgumentError otherwise.

exit_code

Exit code once the streams end, else None (see the sync handle’s note).

poll

Alias of exit_code; never blocks.

cancel

Signal the guest command: SIGINT by default, SIGKILL if force=True.

resize

Set the pty window for a pty=True exec; a no-op otherwise.

resync

Ask a pty=True exec to repaint its current screen; a no-op otherwise. See the sync ExecProcess.resync.

close

Abandon the handle without killing the command.
It releases both streams. The command keeps running and never pauses, and Sail keeps only the most recent output of each stream. Call cancel() instead if the command should stop. wait() raises sail.InvalidArgumentError after close() unless it already resolved a result.

wait

Wait for the exec to complete and return its result.
result.stdout and result.stderr hold each stream’s buffer, its most recent output (1 MiB by default), with stdout_truncated and stderr_truncated set when older output was dropped; to get every byte, read the stream (see AsyncExecProcess). wait() itself never pauses the command and may run while an async generator is still open. With output_mode="pipe", a stream nobody reads pauses the command when its buffer fills, and wait() then waits for as long as the command stays paused. After close() it raises sail.InvalidArgumentError unless a result was already resolved; a repeat wait() returns the cached result.

StdinWriter

File-like write side of an exec’s stdin, reached via proc.stdin.Writes block (with backoff) while the guest buffer is full or the Sailbox is paused, like a real pipe; a completed or stdin-closed exec surfaces as BrokenPipeError.

close

Send EOF; the guest closes the pipe once the backlog drains.

AsyncStdinWriter

Async write side of an exec’s stdin, reached via proc.stdin.await stdin.write(data) applies backpressure (it resolves once the guest accepts the bytes); await stdin.close() sends EOF.

close

Send EOF; the guest closes the pipe once the backlog drains.

function

Decorate a Python function so it can run through Sailbox.exec.

SailFunction

A Python function that can be executed inside a Sailbox.

SailboxFs

Filesystem operations on a Sailbox’s guest, reached via Sailbox.fs.File I/O streams bytes to/from the guest; the directory helpers create, remove, test, and transfer paths. Paths are remote POSIX paths in the guest, accepted as str or PurePosixPath.Writes give what they create to the image’s USER by default (root when the image sets none), the same identity commands run as, so an uploaded file is usable by the code in the Sailbox. Reads and the directory helpers act as root by default, so they work on any path.Every operation except the reads and the directory download takes an optional user in Docker’s USER syntax (name, uid, name:group, or uid:gid; "0:0" is always root). The directory helpers other than the transfers run their command as that user, with its permissions enforced. Writes and the directory upload keep running as root but give that user what they create, like COPY --chown. Reads and the download take no user: a user only decides which paths an operation may touch and who owns what it creates. A read creates nothing in the Sailbox, and a download reads any path as root, the way the reads do. A user other than "0:0" requires a Sailbox whose guest honors requested users; on older Sailboxes these calls fail until Sailbox.upgrade is called.

read

Read a regular file from the Sailbox as bytes.
Loads the entire file into memory. For files larger than a few hundred MiB (model checkpoints, datasets) prefer read_stream, which yields chunks without buffering.

read_stream

Stream a regular file’s contents from the Sailbox as chunks.
The result is iterable both ways, so the same call serves sync and async code:
Chunk sizes depend on network delivery and are bounded by the transfer path. Iterate to completion so the underlying stream is released.

download_url

Mint a URL that serves one file over HTTPS with no API key.
Anyone who holds the URL can read path, and nothing else on the Sailbox, until it expires: expires_in_seconds after minting, one hour by default and seven days at most. attachment serves the file as a download instead of inline. immutable marks the response cacheable for a year; use it only for a path you never overwrite. Share a file by URL covers caching, rewrites, and sleeping or paused Sailboxes.

write_stream

Open a streaming write to a regular file in the Sailbox.
Returns a FileWriter: push chunks with write and confirm with finish (only finish commits the write). Best used as a context manager, which finishes on a clean exit and aborts on an exception:
The async form returns an AsyncFileWriter whose write / finish / abort are awaited:
The file gets mode 0o644 unless mode says otherwise. A user names the owner for the written file and any parent directories the write creates, defaulting to the image’s USER, else root; the write itself always runs as root.

write

Write data to a regular file in the Sailbox.
Missing parent directories are created by default, and the file gets mode 0o644 unless mode says otherwise. A user names the owner for the written file and any parent directories the write creates, defaulting to the image’s USER, else root; the write itself always runs as root. A file-like data is streamed from the source, so it can be larger than memory. See write_files to write several files in one call.

write_files

Write several complete files in one call.
files maps each absolute guest path to its contents: a string (UTF-8), bytes, or a file-like object that is read into memory first. Each file is its own request, up to eight at a time, and every file gets the same create_parents, mode, and user as write. A batch is not atomic across paths: the first failure stops the batch, files that already completed stay written, writes already in flight finish, and the error names the file that failed. A path may appear only once. Use write_stream to stream a large source.

mkdir

Create a directory and any missing parents (like mkdir -p); a no-op if it already exists. A user runs the mkdir as that user, so created directories are owned by it.

remove

Remove a file or directory tree (like rm -rf); a no-op if it is already absent. A user runs the removal as that user, limiting it to what that user may delete.

exists

Whether path exists in the guest. Follows symlinks (like test -e), so a dangling symlink reports False even though ls lists it. A user reports existence as observable by that user: a path the user lacks permission to reach also reports False.

ls

List a directory’s immediate entries as DirEntry records (no recursion). Runs GNU find in the guest, which the default Debian image ships. A missing path raises, as does a path that is not a directory and a listing too large for the exec output cap. An entry whose name is not valid UTF-8 fails the listing, since the path API cannot address it. A user runs the listing as that user, so a directory it may not read raises a permission error.

upload_dir

Upload a local directory’s contents into a directory on the Sailbox.
local_dir’s entries land inside guest_dir, which is created if needed. Entries the upload does not name are left in place; a same-named file is replaced. Uploaded files belong to the image’s USER, the same identity commands run as, so the code in the Sailbox can use them. When the image sets no USER, or that user cannot be resolved in the Sailbox, they belong to root. guest_dir and any missing parents the upload creates get the same owner. Files keep their permission bits, except that the setuid, setgid, and sticky bits are cleared. A user (the same syntax the other operations take) gives the entries to that user instead, like COPY --chown; it must exist in the Sailbox, and like the other operations’ user it requires a Sailbox whose guest honors requested users. The Sailbox’s image must provide tar and gzip, which the transfer uses to ship the directory as one compressed archive; the default images do.

download_dir

Download a directory’s contents from the Sailbox into a local directory.
guest_dir’s entries land inside local_dir, which is created if needed. Entries the download does not name are left in place; a same-named file is replaced. The transfer reads every file in the tree, so download directories of ordinary files: system trees like /proc or /sys hold files that cannot be read as plain data, and downloading them fails. A file that is being written while the download runs is captured as it is at that moment, the way copying a live file would; download after writers finish for a consistent copy. On Windows, a directory that contains symbolic links cannot be downloaded, since Windows restricts creating them. The Sailbox’s image must provide tar and gzip, which the transfer uses to ship the directory as one compressed archive; the default images do.

FileWriter

A streaming write to a file in a Sailbox or on a volume.Push chunks with write and confirm the write with finish; only finish commits it. An unfinished writer aborts on __exit__ (or explicit abort), so a stream that ends without finish is never committed as a completed write. After an abort, a Sailbox file’s state is unspecified and a volume file keeps its previous contents. Usable as a context manager: a clean exit finishes, an exception aborts and propagates. AsyncFileWriter, returned by write_stream.aio, is the async form.

write

Write bytes (or UTF-8 text); writes are chunked at the transport size.

finish

Confirm the write, creating an empty file when nothing was written.

abort

Cancel the write so it is never committed. Idempotent; a no-op after finish.

AsyncFileWriter

A streaming write to a file in a Sailbox or on a volume, with an async interface.Returned by write_stream.aio; same commit semantics as FileWriter with await-able write, finish, and abort. Usable as an async context manager: a clean exit finishes, an exception aborts and propagates.

write

Write bytes (or UTF-8 text); writes are chunked at the transport size.

finish

Confirm the write, creating an empty file when nothing was written.

abort

Cancel the write so it is never committed. Idempotent; a no-op after finish.

FileStream

An iterable stream of file chunks that opens on first use.Opening a Sailbox file can wake the Sailbox. Deferring the open to first iteration keeps read_stream cheap to call, and the async path runs it off the event loop so other tasks keep running. Iterate to completion, or call close (or use it as a context manager) to release the stream early.

close

Stop the stream and release its resources; safe to call twice.

aclose

Async twin of close, run off the event loop.

Volume

A managed volume that can be mounted into one or more Sailboxes.Only the owning organization can attach a volume. Code in a Sailbox can read and write an attached volume unless the mount is read-only. Use separate volumes for workloads that must not share files.Volumes are currently in Alpha. To pilot them, reach out in the Sail Slack: https://join.slack.com/t/sailresearchcrew/shared_invite/zt-41pdcym9j-UU0Ey~A~r6n2H0DQVQsQHQ.Attributes:

find

Get an org-scoped volume by name, optionally creating it.

list

List active volumes for the current organization, newest first.

delete

Delete this volume and return its handle.
The volume becomes unavailable at once.With allow_missing=True an already-deleted volume returns None instead of raising. To delete by name without a handle, use delete_by_name.

delete_by_name

Delete the volume with the given name, returning the deleted handle.
The volume becomes unavailable at once.With allow_missing=True a name that does not resolve to a volume returns None instead of raising.

fs

File operations on this volume: read, write, list, and transfer its files without mounting it in a Sailbox.

from_mount

Load the volume handle for a path mounted into a Sailbox.

VolumeFs

File operations on a volume, reached via Volume.fs.The volume does not have to be mounted anywhere, so it can be filled before any Sailbox uses it. A Sailbox that mounts the volume sees each change within a few seconds of the call that made it returning.Paths are relative to the volume’s root, with or without a leading slash, accepted as str or PurePosixPath: skills/review.md and /skills/review.md name the same file, which a Sailbox mounting the volume at /mnt/shared sees as /mnt/shared/skills/review.md. A path may not contain a .. segment.The operations match SailboxFs, without its user parameter. Files and directories created here belong to root: a command running in a Sailbox as another user can read them, and needs root to change them.A path that does not exist raises FileNotFoundError, and a volume that does not exist raises NotFoundError.

read

Read a regular file from the volume as bytes.
Loads the entire file into memory. For large files prefer read_stream, which yields chunks without buffering.

read_stream

Stream a regular file’s contents from the volume as chunks.
The result is iterable both ways, so the same call serves sync and async code:
Iterate to completion so the underlying stream is released.

write_stream

Open a streaming write to a regular file on the volume.
Returns a FileWriter: push chunks with write and confirm with finish. The file appears at path, replacing any existing file atomically, only when finish succeeds; an aborted writer leaves path as it was. Best used as a context manager, which finishes on a clean exit and aborts on an exception:
The async form returns an AsyncFileWriter whose write / finish / abort are awaited:

write

Write data to a regular file on the volume.
Missing parent directories are created by default, and the file gets mode 0o644 unless mode says otherwise. An existing file is replaced atomically, so a reader sees the old contents or the new, never a mix. A file-like data is streamed from the source, so it can be larger than memory. See write_files to write several files in one call.

write_files

Write several complete files in one call.
files maps each path to its contents: a string (UTF-8), bytes, or a file-like object that is read into memory first. Each file is its own request, up to eight at a time, and every file gets the same create_parents and mode as write. A batch is not atomic across paths: the first failure stops the batch, files that already completed stay written, writes already in flight finish, and the error names the file that failed. A path may appear only once. For a whole tree, upload_dir is faster: it sends one archive instead of one request per file.

mkdir

Create a directory and any missing parents (like mkdir -p); a no-op if it already exists.

remove

Remove a file or directory tree (like rm -rf); a no-op if it is already absent. The volume’s root cannot be removed.

exists

Whether path exists on the volume. Follows symlinks (like test -e), so a dangling symlink reports False even though ls lists it.

ls

List a directory’s immediate entries as DirEntry records, sorted by name (no recursion). With no path it lists the volume’s root. A missing path raises, as does a path that is not a directory. An entry whose name is not valid UTF-8 fails the listing, since a path could not address it.

upload_dir

Upload a local directory’s contents into a directory on the volume.
local_dir’s entries land inside volume_dir, which is created if needed and defaults to the volume’s root. Entries the upload does not name are left in place; a same-named file is replaced atomically. Uploaded entries keep their permission bits (the setuid, setgid, and sticky bits are cleared) and modification times, and symlinks are stored as symlinks. The directory travels as one compressed archive. An upload is not atomic across files: after a failure, the files that already landed stay.

download_dir

Download a directory’s contents from the volume into a local directory.
volume_dir’s entries land inside local_dir, which is created if needed ("/" downloads the whole volume). Entries the download does not name are left in place; a same-named file is replaced. A file that changes while the download runs may be captured partway through the change or fail the download, so download after writers finish. On Windows, a directory that contains symbolic links cannot be downloaded, since Windows restricts creating them. The directory travels as one compressed archive.

Egress policies

See Egress policy for what a policy controls, the document format, and examples. The entries below list the available Python calls.

EgressPolicy

A saved egress policy: a named document that limits which hosts a Sailbox can connect to and can add credentials to the HTTPS requests it sends.A saved policy belongs to your organization. Pass it as egress_policy to sail.Sailbox.create or to sail.Sailbox.set_egress_policy. Its document cannot be edited after creation; save a new policy to change behavior. Sail saves a normalized form of the document (for example, host names are lowercased and defaults are filled in), so reading a policy back can return a different shape with the same behavior. Only a saved policy can reference a secret. For a policy that needs no name or secrets, pass a plain dict in the same places, or one built by allow_all, allow_only, no_egress, or no_network. Obtain a saved policy from create or get; do not construct it directly:
The egress policy guide describes the document.Attributes:

allow_all

The document that lets a Sailbox reach every host: {}.
A Sailbox created without a policy runs under it. sail.Sailbox.clear_egress_policy returns a Sailbox to it.

no_network

The document that cuts a Sailbox off from the network entirely: {"no_network": True}.
No outbound connections, no name lookups, and no ingress ports or SSH. Running commands, the shell, and mounted volumes keep working. No other field may be set beside it.

no_egress

The document that stops every outbound connection: {"allowlist": []}.
Name lookups fail too. Inbound connections still work, so ingress ports and SSH do.

allow_only

The document that lets a Sailbox reach only hosts: {"allowlist": [...]}.
Each host is a hostname, a *. wildcard hostname, an IPv4 address, or an IPv4 range in CIDR form. *.example.com matches every host below it at any depth and never example.com itself. Give at least one; use no_egress to allow none. Inbound ports and SSH are unaffected:

create

Save document under name for your organization.
The document cannot change afterwards. Every ${secrets.NAME} in it must name a secret that already exists. The egress policy guide describes the document.Sail does not retry this call. If the connection ends before the result arrives, list policies before trying again; a second call can create a second policy.

get

Fetch one policy by id, document included.
Raises sail.NotFoundError when no policy has that id.

list

List your organization’s policies as summaries, without documents.
search filters by id or name, case-insensitively, and limit caps the number returned. Fetch a policy’s document with get.

rename

Rename the policy and return the updated policy object.
The document cannot change; create a new policy to change behavior.

delete

Delete the policy.
A policy cannot be deleted while a Sailbox that is not terminated runs under it; the call raises sail.EgressPolicyInUseError until every such Sailbox is given another policy.

Secret

A value an egress policy can insert into matching HTTPS requests.Secrets belong to your organization. Sail never returns a stored value. Get and list calls return only the secret’s name and timestamps.Attributes:

set

Set (create or update) the named secret’s value. An egress policy inserts it with ${secrets.NAME}.
After this call succeeds, the next matching request from any Sailbox whose attached egress policy uses this secret gets the new value.Names start with a letter or number and use letters, numbers, underscores, and dashes (up to 128 characters). Values cannot be empty. They can be up to 64 KiB and cannot contain ASCII control characters such as tabs or line breaks.

get

Fetch one secret’s name and timestamps. The value is never returned.
Raises sail.NotFoundError when no secret has that name.

list

List your organization’s secret names and timestamps, sorted by name.

delete

Delete this secret.
A secret cannot be deleted while an egress policy refers to it; the call raises sail.SecretInUseError until every referencing policy is deleted. Policy summaries from sail.EgressPolicy.list include the secret names they use.To delete by name without a handle, use delete_by_name.

delete_by_name

Delete the named secret. Same contract as delete.

ingress_auth_headers

Headers that authenticate this Sailbox as an ingress allowlist source.
Use these when making HTTP requests from one Sailbox to another listener whose allowlist contains the caller’s app name. The helper is only available inside a Sailbox.

SailTokenCompleter

Tinker TokenCompleter backed by Sail’s raw-token Responses path.Extends TokenCompleter.

TinkerSandbox

Run a tinker-cookbook sandbox on a Sailbox.Implements the cookbook’s sandbox interface, so recipes that take a sandbox (or a sandbox factory, via tinker_sandbox_factory) can execute their rollout commands in an isolated Sailbox instead of on the training machine. Each instance owns one Sailbox for its whole life, and cleanup terminates it; give each sandbox its own Sailbox, since two sandboxes sharing one would share its filesystem and processes and the first cleanup would terminate it for both. The factory creates a fresh Sailbox per sandbox; constructing directly is for supplying your own, such as one restored from a warmed checkpoint.timeout_seconds is the sandbox’s lifetime budget: once it has elapsed, the next operation terminates the Sailbox and raises the cookbook’s SandboxTerminatedError. None means no budget.

sandbox_id

The backing Sailbox’s id.

run_command

Run a shell command in the Sailbox and return its SandboxResult.
max_output_bytes keeps only the first bytes of each output stream; without it the full output is returned, up to a large safety ceiling that keeps a runaway stream from exhausting the training process’s memory. A command that outlives timeout (seconds) is killed and reported with metrics["timed_out"] set. Errors from the Sailbox surface as a result with exit code -1, except a terminated or lost Sailbox, which raises the cookbook’s SandboxTerminatedError.

read_file

Read a file from the Sailbox into a SandboxResult’s stdout.
max_bytes keeps the file’s first bytes (without it the whole file, up to a large safety ceiling), and timeout (seconds) bounds the whole read. A missing or unreadable file is reported as a result with exit code 1 rather than raised, matching the cookbook’s contract, and so is a read that runs out of time.

write_file

Write a file into the Sailbox, marked executable when asked.
timeout (seconds) bounds the write; one that runs out of time is reported as a result with exit code 1.

send_heartbeat

Check the sandbox’s lifetime budget.
A Sailbox stays alive without keep-alives, so the heartbeat sends nothing; it only enforces timeout_seconds, terminating the Sailbox and raising the cookbook’s SandboxTerminatedError once the budget has elapsed. timeout is part of the cookbook’s heartbeat signature and is unused here, since there is no request for it to bound.

cleanup

Terminate the backing Sailbox. Safe to call more than once (termination is idempotent for an already-gone Sailbox); a cancellation arriving mid-cleanup still lets the termination finish, within a bounded grace, before propagating, so the Sailbox does not stay running and billable.

tinker_sandbox_factory

Create a TinkerSandbox for a tinker-cookbook environment.
Pass this function (or a functools.partial of it, to preset the keyword arguments) wherever the cookbook accepts a sandbox factory; being a module-level function, it pickles by reference, so it survives the cookbook’s process boundaries.image_ref takes precedence when given. Otherwise, the image comes from [environment].docker_image in the task.toml next to env_dir, or from env_dir / "Dockerfile" with env_dir as its build context. Docker-style short references are accepted (python:3.11). The registry image or Dockerfile must produce a Debian- or Ubuntu-based filesystem. The Sailbox is created in the app app (default $SAIL_APP or "tinker", created on first use) and timeout_seconds becomes the sandbox’s lifetime budget.

SailboxEnvironment

A Harbor environment whose commands run in a Sailbox.Extends ComposeServiceOpsMixin, BaseEnvironment.start creates the Sailbox from the task’s image: a declared docker_image is pulled from its registry (Docker-style short references are accepted), and a task that ships an environment/Dockerfile instead has it built into a Sailbox image. Task and persistent environment variables apply to every command, and a prebuilt-image task’s environment/ directory is uploaded into its working directory, the same way Harbor’s other cloud providers do.A task that ships an environment/docker-compose.yaml runs as a Docker Compose project inside the Sailbox, with commands running in its main service and the service images built into the Sailbox image. If the task also declares a docker_image or ships an environment/Dockerfile, main runs on the image from that source. The Harbor guide lists the compose features and limits Sail supports.stop puts the Sailbox to sleep, so a kept environment stops billing and resumes with its filesystem intact on the next start; deleting the environment terminates the Sailbox. In Compose mode the whole project sleeps, wakes, and terminates with the Sailbox.Harbor rejects a task that needs GPUs, TPUs, Windows, or IPv6 allowlist entries before it starts, since Sailboxes do not offer them. Mounts declared for a task are ignored, except in a Compose project, where they are bound into main from the Sailbox’s filesystem.

start

Create the environment’s Sailbox, building its image if needed.
Starting an environment that was stopped without deletion resumes its sleeping Sailbox, filesystem intact, instead of creating a new one.

exec

Run a shell command in the environment and return its result.
In Compose mode the command runs inside the main service; otherwise it runs in the Sailbox itself.

upload_file

Copy a local file in, keeping its permission bits.

download_file

Copy a file out to the local filesystem.

upload_dir

Copy a local directory’s contents in.

download_dir

Copy a directory’s contents out to a local directory.

stop

Stop the environment: put its Sailbox to sleep, or terminate it.
A sleeping Sailbox stops billing and keeps its state, so starting the same environment again resumes it; deletion is permanent. In Compose mode the project’s containers sleep and wake with the Sailbox, and termination takes the whole project with it.

Config

SDK configuration resolved from the environment and ~/.sail.Sail resolves the API key and service endpoints, with environment variables taking precedence over the stored ~/.sail credentials. Set SAIL_API_KEY (or run sail auth login) to authenticate. SAIL_API_URL, SAILBOX_API_URL, and SAILBOX_INGRESS_URL override individual endpoints, for custom or self-hosted stacks. Configuration is resolved once per process; in a long-lived process, call sail.reset_transports() after changing these variables. ingress_base and ingress_scheme describe how a listener’s public URL is built from the Sailbox id and port when the server does not return one: "path" addresses <base>/_sailbox/{id}/{port}, "subdomain" addresses <sailbox>-<port>.<base host>.

from_env

Load SDK config, raising ValueError when no API key is configured.

from_env_optional_api_key

Load SDK config without requiring an API key.
Like from_env() but does not raise when no key is configured, so endpoints still resolve for paths that do not need to authenticate (such as building a listener’s public URL).

Types

Plain data types accepted by and returned from the calls above.

IngressPort

A guest port to expose for ingress.protocol selects how the port is published:
  • "http" (the default) exposes the port as an HTTP service with a stable HTTPS URL.
  • "tcp" exposes the port as a byte-transparent raw-TCP service reachable at a stable host and port by any TCP client (for example a database client such as psql -h <host> -p <port>).
Sailbox.create(ingress_ports=...) also accepts a bare int as shorthand for IngressPort(port) (i.e. an HTTP port).allowlist restricts which sources may connect to this port. An entry that reads as an address or a range (e.g. ["203.0.113.0/24"]) matches source IPs; every other entry is a Sail app name whose Sailboxes may connect. An app name cannot read as an address or a range, and cannot contain a /. An address must not carry an IPv6 zone, such as fe80::1%eth0, which names an interface on one machine rather than a source. App-name entries are supported on "http" listeners only. Raw-TCP connections carry no source app identity, so "tcp" allowlists must be addresses or ranges. Each port carries its own allowlist. An empty/omitted list means any source may connect.Exposing a well-known unauthenticated service port (e.g. a database) as raw TCP without an explicit allowlist is rejected. Use source restrictions, or pass ["0.0.0.0/0", "::/0"] to explicitly allow every source.Attributes:

HttpEndpoint

The routable HTTPS address of an "http" listener.Attributes:

TcpEndpoint

The host and port to connect to for a "tcp" listener.Attributes:

Listener

An exposed guest port.guest_port is the guest port you exposed. endpoint is how you reach it: an HttpEndpoint for "http" listeners or a TcpEndpoint for "tcp" listeners. It is None until the listener is routable.Attributes:

SailboxPage

One page of Sailbox.list_page results plus the server’s pagination envelope.Attributes:

SailboxCheckpoint

A durable checkpoint handle that can be used to start new Sailboxes.Attributes:

UpgradeResult

The outcome of a Sailbox runtime upgrade.Attributes:

SailboxDeprecation

Actionable notice that a Sailbox’s runtime should be upgraded.Attributes:

SailboxVolumeMount

One volume attached to a Sailbox and where it is mounted.Attributes:

AutoSleep

When Sail may put a Sailbox to sleep on its own.Sail sleeps idle Sailboxes and wakes them when needed. Waking takes a few seconds, so the first request or command after a sleep is slower than usual.Build one with default, not_before, or never. Sail sleeps a Sailbox once it has been truly idle for a minimum time, 30 seconds by default. not_before sets your own minimum. See Autosleep.Calling Sailbox.sleep yourself is unaffected, and so are Sailbox.pause, Sailbox.resume, and scheduled wakes.Attributes:

default

Let Sail decide, on its own timing. The default.

not_before

Let Sail decide, but not before this much idle time.
This idle window replaces Sail’s default. Whole-second values from 1 through 3600 are accepted. A value of 0 is the same as default, and is stored and read back that way. Other numeric values are rejected; use never instead.

never

Stop Sail sleeping a Sailbox on its own.

OutputMode

What happens when a stream’s output buffer fills. Each stream has its own buffer, 1 MiB by default (output_buffer_bytes on Sailbox.exec). Accepted as the enum or its string value.Extends str, Enum.Sending cancel(), and the exec timeout, end every pause: from then on each stream keeps only its most recent bytes, so a reader more than a buffer behind skips. A command that ignores the cancel signal keeps running that way; cancel(force=True) stops it. The command keeps its original timeout. If this handle attaches to a command launched earlier under the same idempotency_key, this handle’s pause deadline starts when the attachment succeeds, so it can release the pauses one full timeout after that; cancel() and close() release them at once.Attributes:

DirEntry

One entry in a directory listing from SailboxFs.ls or VolumeFs.ls.Attributes:

ExecResult

The completed result of a Sailbox command. It holds the most recent output of each stream (up to the exec’s output_buffer_bytes, 1 MiB by default), the exit code, timeout status, and truncation flags.Attributes:

PtyConfig

The pseudo-terminal a pty exec runs under. Every field has a default, so PtyConfig() (or pty=True) is a usable terminal.Attributes:

EgressPolicySummary

A policy as returned by EgressPolicy.list, with usage counts but without the document. Fetch the full policy with EgressPolicy.get.Attributes:

SailboxEgressPolicy

A Sailbox’s egress policy as Sail enforces it, read from sail.Sailbox.egress_policy.document is always present: a Sailbox created without a policy has the empty document {}, so every host is reachable and no rules apply. policy_id and name identify the saved EgressPolicy when one is attached and are None when the Sailbox was given a document directly.Attributes:

GuestPath

A path inside the guest: a str or a PurePosixPath. Guest paths are remote POSIX paths, independent of the local platform.

VolumePath

A path on a volume, relative to the volume’s root: a str or a PurePosixPath. Volume paths are remote POSIX paths, independent of the local platform.

FileContents

The contents of one file passed to write or write_files: a str (written as UTF-8), a bytes-like object, or a readable file-like object that is read to its end first.

DEFAULT_LIST_LIMIT

Default page size for Sailbox.list and Sailbox.list_page.

Errors

Exceptions raised by this SDK surface. Every one of them extends SailError, so except sail.SailError catches them all. SailDeprecationWarning is the one entry below that is not an error: it is a warning the SDK emits through Python’s warnings module.

SailError

Base class for Sail SDK errors.Every operation failure the SDK raises derives from this class, and the classes that match a Python builtin also inherit it (for example NotFoundError is a LookupError), so except sail.SailError and builtin-based handlers both work. A few argument-type mistakes raise the plain builtin TypeError.Attributes:

NotFoundError

Raised when a requested Sailbox, app, volume, checkpoint, secret, or egress policy is not found.Extends SailError, LookupError.

PermissionDeniedError

Raised for a missing or invalid API key, or insufficient scope.Extends SailError, PermissionError.

InvalidArgumentError

Raised when an argument or the SDK configuration is rejected as invalid.Extends SailError, ValueError.

InternalError

Raised for an unexpected internal SDK failure.Extends SailError, RuntimeError.

FileNotFoundError

Raised when a guest file operation references a path that does not exist.Extends SailError, builtins.FileNotFoundError.

BrokenPipeError

Raised when a stdin write hits a command that already finished.Extends SailError, builtins.BrokenPipeError.

TimeoutError

Raised when a transport attempt exceeds its deadline.Extends SailError, builtins.TimeoutError.

TransportError

Raised when the transport cannot establish or maintain a connection.Extends SailError, ConnectionError.

ApiError

Raised for any other non-2xx API response.Extends SailError, RuntimeError.RuntimeError inheritance keeps generic retry-on-RuntimeError loops working; prefer branching on retryable.

SecretInUseError

Raised when deleting a secret that egress policies still refer to.Extends ApiError.Delete those policies first, then delete the secret.

EgressPolicyInUseError

Raised when deleting an egress policy that a Sailbox still runs under.Extends ApiError.Terminated Sailboxes do not count. Give every other Sailbox using the policy another policy first, then delete it.

SailDeprecationWarning

Warning that a Sail client or Sailbox runtime should be upgraded.Extends UserWarning.

SailboxError

Base class for Sailbox-specific SDK errors.Extends SailError.

SailboxCreationError

Raised when Sailbox creation fails.Extends SailboxError.

ImageBuildError

Raised when a custom image build fails.Extends SailboxError.

SailboxExecutionError

Base class for Sailbox exec-related SDK errors.Extends SailboxError.

SailboxTerminatedError

Raised when the Sailbox no longer exists.Extends SailboxExecutionError.

SailboxExecRequestNotFoundError

Raised when a wait references an unknown exec request.Extends SailboxExecutionError.

SailboxHostLostError

Raised when the machine hosting your Sailbox failed before the command finished.Extends SailboxExecutionError.The command may have run only partially, and its output is gone. The run cannot be resumed. Calling Sailbox.exec again starts it over from the beginning, so any side effects the partial run applied will happen again. The Sailbox itself recovers automatically, so you do not need to resume it.

SailboxFunctionError

Raised when a Python function fails while running in a Sailbox.Extends SailboxExecutionError.

SailboxFunctionSerializationError

Raised when a Python function payload or result cannot be serialized.Extends SailboxExecutionError.

CommandFailedError

Raised by run(check=True) when the command exits nonzero or times out.Extends SailboxExecutionError.Carries the completed sail.ExecResult as result.

InferenceError

Base class for Sail inference wrapper errors.Extends SailError.

InferenceHTTPError

Raised when a Sail inference endpoint returns an HTTP error.Extends InferenceError.