Skip to main content
sail.Sailbox is a sandbox instance on the Sail platform. You create one with Sailbox.create(), then run commands, transfer files, expose ports, and checkpoint or pause it. For a guided walkthrough, see the Sailboxes guide; this page is the API reference.
import sail

sb = sail.Sailbox.create(
    app=sail.App.find(name="example-app", mint_if_missing=True),
    image=sail.Image.debian_arm64,
    name="sandbox-1",
)
print(sb.sailbox_id, sb.status)

Attributes

AttributeTypeDescription
sailbox_idstrStable control-plane identifier. The primary external handle for the sandbox.
namestrHuman-readable name supplied at create time.
statusstrCurrent lifecycle status (e.g. running, paused, sleeping).
exec_endpointstrEndpoint the SDK dials for exec, file, listener, and network calls. Treat it as opaque.
Treat sailbox_id as the durable handle. Sailbox.get and Sailbox.list return a trimmed SailboxInfo instead of a Sailbox because they intentionally omit exec_endpoint; use Sailbox.connect to get a fully operable handle from an id.

Sailbox.create

@classmethod
def create(
    *,
    image: ImageDefinition,
    app: App,
    name: str,
    image_build_timeout: int = 1800,
    max_cpu: int | None = None,
    max_memory_mib: int | None = None,
    max_disk_gib: int | None = None,
    ingress_ports: list[int | IngressPort] | None = None,
    ssh: bool | str = False,
) -> Sailbox
Creates a new sailbox. Custom image definitions are built first, then a synchronous request returns once the VM is running (or creation has failed). The max_* parameters are resource-control limits, not billing reservations: Sailbox billing uses observed CPU, memory, and disk usage.
ParameterDefaultDescription
imagerequiredA sail.Image value or custom image definition.
apprequiredThe owning sail.App, usually from App.find().
namerequiredHuman-readable sailbox name.
image_build_timeout1800Seconds to wait for a custom image build before creating the VM. Must be > 0.
max_cpu4Maximum vCPU count for resource control. Must be between 1 and 4.
max_memory_mib8192Maximum memory in MiB for resource control. Must be between 1024 and 8192.
max_disk_gib32Maximum writable state disk in GiB for resource control. Must be between 1 and 32.
ingress_portsNoneGuest ports to expose. Each entry is a bare int (HTTP shorthand) or an IngressPort.
sshFalseTrue to get an SSH-ready box in one call; a key string or .pub path to override the key. See below.
Returns a running Sailbox. Raises sail.SailboxCreationError on creation failure, PermissionError on 401/403, TypeError if image is not a sail.Image value, and ValueError for out-of-range max_cpu/max_memory_mib/max_disk_gib or invalid ingress_ports.
The older cpu, memory_mib, and disk_gib keyword arguments are accepted as compatibility aliases for max_cpu, max_memory_mib, and max_disk_gib.
Use sail.Image.debian_arm64 or sail.Image.debian_arm for most workloads; they pin the image to your local Python version so @sail.function can deserialize local bytecode. See Images & Functions.

SSH in one call

Passing ssh=True adds an SSH server to the image, exposes guest port 22 as raw TCP, installs your local public key (~/.ssh/id_ed25519.pub then id_rsa.pub, or pass a key string / .pub path via ssh="..."), and starts sshd. Read the port-22 listener’s endpoint for the connect command:
sb = sail.Sailbox.create(
    app=app,
    image=sail.Image.debian_arm64,
    name="ssh-box",
    ssh=True,
)
endpoint = sb.listener(22).wait().endpoint
print(f"ssh -p {endpoint.port} root@{endpoint.host}")
SSH must be set up at create time: guest port 22 ingress cannot be added to a running sailbox, so start_sshd only works on a box created with ssh=True.

Sailbox.connect

@classmethod
def connect(sailbox_id: str) -> Sailbox
Returns a fully operable handle for an existing sailbox by id. Unlike Sailbox.get, this goes through the lifecycle resume route, which validates access and returns the current exec_endpoint. If the sailbox is paused or sleeping it is resumed first; if it is already running the backend just verifies the current placement. Raises LookupError if the sailbox cannot be resumed (e.g. terminated), PermissionError on auth failures.
sb = sail.Sailbox.connect("sb_...")

Sailbox.get

@classmethod
def get(sailbox_id: str) -> SailboxInfo
Fetches a single sailbox owned by the current org as read-only monitoring state. Returns a SailboxInfo (no exec_endpoint), so use Sailbox.connect when you need to run commands. Unknown and wrong-org ids both raise LookupError (the server returns 404 for both to avoid leaking ownership across orgs).

Sailbox.list

@classmethod
def list(
    *,
    app: str | None = None,
    status: str | None = None,
    search: str | None = None,
    max_guest_schema_version: int | None = None,
    limit: int = 50,
    offset: int = 0,
) -> list[SailboxInfo]
Lists sailboxes for the current org. Filters are applied server-side; app matches against app_id (resolve a name through App.find first). max_guest_schema_version keeps only sailboxes whose in-guest agent version is at or below the given version (rows that predate version stamping always match). Use it to find sailboxes that need upgrade. Returns just the rows. Use Sailbox.list_page for the pagination envelope.

Sailbox.list_page

@classmethod
def list_page(
    *,
    app: str | None = None,
    status: str | None = None,
    search: str | None = None,
    max_guest_schema_version: int | None = None,
    limit: int = 50,
    offset: int = 0,
) -> SailboxPage
Same call as Sailbox.list, but returns a SailboxPage with the rows plus the server’s limit/offset/total/has_more envelope.

exec

def exec(
    command: str | SailFunction,
    *function_args,
    timeout: int | None = None,
    background: bool = False,
    cwd: str | None = None,
    idempotency_key: str | None = None,
    args: list | tuple | None = None,
    kwargs: Mapping | None = None,
) -> SailboxExecRequest | Any
Runs a shell command or a decorated Python function in the sailbox.
  • For a shell command (str), returns a SailboxExecRequest immediately; call .wait() to collect output.
  • For a SailFunction, exec blocks and returns the function’s return value directly.
ParameterDefaultDescription
commandrequiredA shell command string, or a @sail.function-decorated callable.
*function_argsPositional args for a SailFunction (alternatively pass args=).
timeoutNoneCommand runtime budget in seconds. Omit for no SDK-imposed limit. Must be > 0 when set.
backgroundFalseLaunch through a detached shell; wait() only waits for the launcher to succeed. Not supported for functions.
cwdNoneWorking directory to run the command from.
idempotency_keyNoneDefaults to a generated key, which makes retrying initial submission safe.
args / kwargsNoneFunction arguments, as an alternative to positional *function_args.
result = sb.exec("echo hi", timeout=5).wait()
print(result.stdout, result.returncode)
Multiple execs can run on the same sailbox concurrently; coordinate access to shared files and ports in your own commands. Exec is unavailable while the sailbox is paused (call resume() first), which raises sail.SailboxExecutionError.

read

def read(path: str) -> bytes
Reads a regular file from the sailbox as bytes. Loads the whole file into memory; for very large files (checkpoints, datasets) prefer read_stream. Raises FileNotFoundError if the path does not exist, ValueError if path is empty.
data = sb.read("/workspace/output.txt")
print(data.decode())

read_stream

def read_stream(path: str) -> Iterator[bytes]
Yields a regular file’s contents as chunks (currently ~1 MiB each) without buffering the whole file. The underlying channel stays open until the iterator is exhausted or closed, so iterate to completion rather than holding it open.
with open("local.bin", "wb") as f:
    for chunk in sb.read_stream("/workspace/large.bin"):
        f.write(chunk)

write

def write(
    path: str,
    data: str | bytes | bytearray | memoryview | IOBase,
    *,
    create_parents: bool = True,
    mode: int | None = None,
) -> None
Writes data to a regular file in the sailbox. Missing parent directories are created by default. path must be absolute.
ParameterDefaultDescription
pathrequiredAbsolute destination path.
datarequiredBytes, string, or a file-like object (streamed in chunks).
create_parentsTrueCreate missing parent directories.
modeNonePOSIX permission bits (0–0o777). Defaults to 0o644 when omitted.
sb.write("/workspace/input.txt", "hello\n")

request

def request(
    method: str,
    url: str,
    *,
    params: dict[str, str] | None = None,
    headers: dict[str, str] | None = None,
    data: bytes | str | None = None,
    json: Any | None = None,
    timeout: int | None = None,
    durability: str = "tracked",
    idempotency_key: str | None = None,
) -> SailboxRequest
Issues a tracked HTTP request from inside the sailbox and returns a SailboxRequest handle. data and json are mutually exclusive; idempotency_key is required (must be non-empty). timeout defaults to 30 seconds server-side when omitted.
Sailboxes have transparent outbound egress, so for most use cases you can make ordinary HTTP calls from inside an exec. request() exists for cases that need a durably tracked request record.

listener

def listener(port: int) -> SailboxListener
Returns the SailboxListener for a single exposed guest port. Raises LookupError if the port was not exposed via ingress_ports at create time (with a message telling you to expose it), PermissionError/RuntimeError on other failures.
listener = sb.listener(3000)
listener.wait(timeout=60)
print(listener.endpoint.url)

listeners

def listeners() -> list[SailboxListener]
Returns one SailboxListener per exposed guest port.
for listener in sb.listeners():
    print(listener.port, listener.protocol, listener.route_status, listener.endpoint)

start_sshd

def start_sshd(
    public_key: str | None = None,
    *,
    wait: bool = True,
    timeout: float = 60.0,
) -> TcpEndpoint | None
Installs your SSH public key and (re)starts sshd. create(ssh=True) runs this for you, so you normally only reach for it to bring sshd back up if the daemon stops inside a running box. Safe to re-run: it generates the box’s host key once and never rotates it, so a caller’s known_hosts stays valid. public_key defaults to ~/.ssh/id_ed25519.pub then ~/.ssh/id_rsa.pub; pass a key string or a path to a .pub file to override. By default this blocks until the port-22 listener is reachable and returns its TcpEndpoint; pass wait=False to skip the probe and return None. Raises LookupError if guest port 22 is not exposed (the box was not created with ssh=True), since the ingress port cannot be added to a running sailbox.

checkpoint

def checkpoint() -> None
Durably checkpoints this running sailbox without stopping it.

upgrade

def upgrade() -> bool
Upgrades this sailbox’s in-guest agent to the latest version, picking up new sailbox features, fixes, and performance improvements without recreating the box. 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 (unflushed application state gets power-loss semantics). A paused or sleeping sailbox is upgraded without waking. The upgrade is recorded and applies automatically at the next wake. Returns True when the upgrade was applied immediately (the sailbox was running) and False when it will apply at the next wake. Already-up-to-date sailboxes return True without rebooting.

fork

def fork(
    *,
    name: str | None = None,
    timeout_seconds: int | None = None,
) -> Sailbox
Creates a new running sailbox from this sailbox’s current memory and disk state and returns its Sailbox handle. Running parents are checkpointed first; sleeping and paused parents fork from their last checkpoint without waking. timeout_seconds, when set, must be > 0.

pause

def pause() -> None
Checkpoints and pauses this sailbox until it is explicitly resumed. After pause(), status becomes paused and exec is rejected until resume().

sleep

def sleep() -> None
Checkpoints and sleeps this sailbox until traffic or an explicit resume() wakes it. After sleep(), status becomes sleeping.

resume

def resume() -> Sailbox
Resumes this sailbox, refreshing status and exec_endpoint on the handle. Raises LookupError if the sailbox is terminated and cannot be resumed.

terminate

def terminate() -> None
Permanently terminates this sailbox. Idempotent: terminating an already-terminated sailbox succeeds.

Supporting types

IngressPort

A guest port to expose through the platform edge. Sailbox.create also accepts a bare int as shorthand for IngressPort(port) (an HTTP port).
FieldDefaultDescription
guest_portrequiredGuest port to expose (1–65535).
protocol"http""http" for a stable HTTPS URL, or "tcp" for a byte-transparent raw-TCP host/port.
cidr_allowlistNoneSource-IP CIDR prefixes allowed to connect. "tcp" ports only.
allow_publicFalseFor "tcp": confirm the port is intentionally reachable by the whole internet.
Reserved ports: guest port 22 cannot be an HTTP port (expose it as tcp for SSH) and 10000/10001/15001/15002 are reserved for in-guest platform services. Exposing a well-known unauthenticated service port (database/cache/search, e.g. 5432, 6379) as raw TCP with neither cidr_allowlist nor allow_public=True is rejected.
sb = sail.Sailbox.create(
    app=app,
    image=sail.Image.debian_arm64,
    name="db-box",
    ingress_ports=[80, 443, sail.IngressPort(5432, "tcp", cidr_allowlist=["203.0.113.0/24"])],
)

SailboxExecRequest

A handle to a submitted shell-command exec.
FieldDescription
sailbox_idOwning sailbox id.
exec_request_idDurable id for this exec.
exec_endpointWorker-proxy endpoint the exec runs through.
backgroundWhether the exec was launched in the background.
idempotency_keyThe idempotency key used for submission.
def wait(*, retry_timeout: float = 30.0) -> SailboxExecResult
Waits for the exec to complete and returns a SailboxExecResult. For foreground execs this waits for the command itself; for background execs it waits only for the detached launcher. Ctrl-C sends SIGINT and resumes waiting; a second Ctrl-C escalates to SIGKILL. Raises sail.SailboxWorkerLostError if the assigned worker was lost, or sail.SailboxExecInterruptedError if the exec was interrupted before completion and is retryable.
def cancel(*, force: bool = False, retry_timeout: float = 0.0) -> None
Signals the guest command: SIGINT by default, SIGKILL if force=True. Idempotent on the server.

SailboxExecResult

The output of a completed exec.
FieldTypeDescription
stdoutstrCaptured standard output.
stderrstrCaptured standard error.
returncodeintProcess exit code.

SailboxListener

An exposed guest port and how to reach it.
FieldTypeDescription
sailbox_idstrOwning sailbox id.
portintThe exposed guest port.
protocolstr"http" or "tcp".
route_statusstrRoute lifecycle status (e.g. LISTENER_ROUTE_STATUS_ACTIVE).
endpointHttpEndpoint | TcpEndpoint | NoneHow to reach the port; None until routable or when ingress for the protocol is disabled.
def status() -> SailboxListener
def wait(timeout: float = 60.0, poll_interval: float = 1.0) -> SailboxListener
status() re-fetches the listener. wait() blocks until the route is active and the endpoint is reachable (an HTTP probe to the url, or a TCP connectivity check to the host/port), then returns the ready listener. Raises TimeoutError if it does not become reachable within timeout.

HttpEndpoint

The routable HTTPS address of an "http" listener.
FieldTypeDescription
urlstrThe HTTPS URL to reach the guest service.

TcpEndpoint

The host and port to connect to for a "tcp" listener.
FieldTypeDescription
hoststrHostname to dial.
portintPort to dial.

SailboxRequest

A handle to a tracked HTTP request issued via request.
FieldTypeDescription
sailbox_idstrOwning sailbox id.
request_idstrDurable request id (also available as .id).
exec_endpointstrWorker-proxy endpoint.
statusstrRequest status.
responseSailboxResponse | NoneThe response once completed.
error_messagestrError detail if the request failed.
Methods: refresh() re-fetches current state, wait(timeout=None) blocks until completion, and cancel() cancels the request. Each returns the updated SailboxRequest.

SailboxResponse

The response of a completed SailboxRequest.
MemberTypeDescription
status_codeintHTTP status code.
headersdict[str, str]Response headers.
contentbytesRaw response body.
textstr (property)Body decoded as UTF-8.
json()AnyBody parsed as JSON.

SailboxInfo

A read-only snapshot returned by Sailbox.get and Sailbox.list. It deliberately omits exec_endpoint; use Sailbox.connect for an operable handle.
FieldTypeDescription
sailbox_idstrSailbox id.
app_id / app_namestrOwning app.
image_idstrImage the sailbox runs.
namestrSailbox name.
statusstrLifecycle status.
memory_mib / vcpu_count / state_disk_size_gibintConfigured resource-control maxima.
cpu_requested_vcpuintConfigured vCPU maximum.
cpu_used_vcpufloatLatest observed vCPU usage.
memory_requested_bytes / memory_used_bytesintConfigured max vs observed memory.
disk_requested_bytes / disk_used_bytesintConfigured max vs observed disk.
architecturestrCPU architecture.
guest_schema_versionint | NoneIn-guest agent version the sailbox last booted with. None means it predates version stamping (the oldest possible version).
error_messagestr | NoneFailure detail, when applicable.
checkpoint_generationintMonotonic checkpoint counter.
started_at / last_checkpointed_atstr | NoneTimestamps.
created_at / updated_atstrTimestamps.
Observed-usage fields reflect the latest live sample within roughly the last two minutes, falling back to zero when no recent sample is available.

SailboxPage

One page of Sailbox.list_page results.
FieldTypeDescription
itemslist[SailboxInfo]The rows on this page.
limitintPage size used.
offsetintPage offset used.
totalintTotal matching sailboxes.
has_moreboolWhether more rows exist beyond this page.