Skip to main content

Python SDK for the Sail sandbox platform

Project description

sail-sdk

Python SDK for Sail sailboxes.

Sailboxes are persistent Linux sandboxes managed by Sail. Use the sail-sdk Python package to create a VM, run shell commands, expose ports, make tracked network requests, checkpoint state, pause, resume, and terminate it.

Sailboxes are currently in beta. APIs and operational behavior may change as we stabilize the product.

Install

pip install sail-sdk
uv add sail-sdk

Set your API key before using the SDK:

export SAIL_API_KEY=sk_...

By default, the SDK connects to production. For internal dev or staging environments, set SAIL_MODE=dev or SAIL_MODE=staging. For an isolated local tunnel created by just redeploy-sailbox-dev, set SAIL_MODE=local.

Create a sailbox

import sail

app = sail.App.find(name="example-app", mint_if_missing=True)

sb = sail.Sailbox.create(
    app=app,
    image=sail.Image.debian_arm64,
    name="sandbox-1",
    cpu=1,
    memory_mib=1024,
    disk_gib=8,
)

print(sb.sailbox_id)
print(sb.status)

Sailbox.create() returns after the VM is running. The main create arguments are:

Argument Default Description
app Required A sail.App, usually from sail.App.find()
image Required A sail.Image value or custom image definition
name Required Human-readable sailbox name
image_build_timeout 1800 Seconds to wait for custom image builds before creating the VM
cpu 1 vCPU count; must be greater than 0
memory_mib 1024 Memory in MiB; must be between 1024 and 65536
disk_gib 8 Writable state disk in GiB; must be between 1 and 64
ingress_ports None Guest ports to expose publicly

Sailboxes should use sail.Image.debian_arm64 or sail.Image.debian_arm. We have plans to support AMD64 images soon - please contact us if you would like us to prioritise this.

Run commands

exec() starts a shell command and returns a SailboxExecRequest. Call wait() to retrieve stdout, stderr, and the return code.

result = sb.exec("echo hi", timeout=5).wait()

print(result.stdout)
print(result.stderr)
print(result.returncode)

timeout is the command runtime budget in seconds. Omit it to run without an SDK-provided runtime limit. Only one exec request can be active on a sailbox at a time; if another command is running, the SDK raises sail.SailboxExecAlreadyRunningError.

Pass cwd to run the command from a specific working directory. With background=True, Sail launches the process through a detached shell and wait() only waits for that launcher to succeed.

Run Python functions

Decorate a Python function with @sail.function() and pass it to exec() to run it inside the sailbox. For functions, exec() waits for completion and returns the function's return value directly. Sail runs the function with the image's python3.

@sail.function()
def add(x: int, y: int) -> int:
    return x + y

value = sb.exec(add, 2, 3, timeout=30)
print(value)  # 5

Sail serializes the decorated function, arguments, and return value with cloudpickle. For imagebuilder-backed images, the SDK records the local Python version in the image spec by default, and imagebuilder installs that version as the image's python3 before running pip_install steps. Function exec uses the sailbox python3 as-is and does not download a separate interpreter at call time. If a stock image's Python differs from the local SDK process, function execution still runs best-effort.

Function execution is synchronous. background=True is not supported for functions. Remote exceptions are raised as sail.SailboxFunctionError with the remote traceback attached.

This beta path sends serialized function payloads and return values through the existing exec RPC. Keep arguments and return values small; for large dataframes or artifacts, write data from inside the sailbox and return a small reference.

Expose ports

Pass ingress_ports when creating the sailbox, then start a service inside the VM and fetch its listener URL.

sb = sail.Sailbox.create(
    app=app,
    image=sail.Image.debian_arm64,
    name="web-demo",
    ingress_ports=[3000],
)

sb.exec("python3 -m http.server 3000", background=True, cwd="/srv/app").wait()

listener = sb.listener(3000)
listener.wait(timeout=60)

print(listener.url)
print(listener.route_status)

Use listeners() to list every exposed port:

for listener in sb.listeners():
    print(listener.port, listener.url, listener.route_status)

Ports must be unique and between 1 and 65535. Ports 22 and 10000 are reserved by the platform.

Tracked network requests

Use request() to ask the worker proxy to make an HTTP request and track its result. idempotency_key is highly recommended so that retries do not send duplicate outbound requests.

req = sb.request(
    "POST",
    "https://example.com/api",
    headers={"X-Source": "sailbox"},
    json={"hello": "world"},
    idempotency_key="example-request-1",
)

completed = req.wait(timeout=30)
print(completed.status)

if completed.response:
    print(completed.response.status_code)
    print(completed.response.headers)
    print(completed.response.text)
    print(completed.response.json())

params, headers, data, json, timeout, and durability are supported. data and json are mutually exclusive. A SailboxRequest can be refreshed, waited on, or cancelled:

req.refresh()
req.wait(timeout=30)
req.cancel()

refresh() re-fetches the current status of the tracked request from the worker proxy and updates the same SailboxRequest object in place. It does not wait for completion, does not retry the outbound HTTP request, and does not send a new request to the destination URL. Use it when you want to poll manually or inspect whether a request has completed before deciding what to do next. Use wait() when you want to block until the request reaches a terminal state.

Long-running services

Create a normal sailbox and start long-running processes with background exec. There is no separate daemon creation API. Existing guest-to-egress connections can remain open while background processes run. Sailboxes are checkpointed only when you call checkpoint(), pause(), or sleep(), or when Sail receives an AWS preemption signal for the host.

sb = sail.Sailbox.create(
    app=app,
    image=sail.Image.debian_arm64,
    name="web-daemon",
    ingress_ports=[3000],
)

sb.exec("python3 -m http.server 3000", background=True, cwd="/srv/app").wait()
sb.checkpoint()

Lifecycle

sb.checkpoint()  # Snapshot while keeping the sailbox running
sb.pause()       # Checkpoint and pause until explicit resume
sb.sleep()       # Checkpoint and sleep until network ingress, exec, or resume
sb.resume()      # Resume a paused or sleeping sailbox
sb.terminate()   # Permanently destroy the sailbox

After pause(), exec() raises sail.SailboxExecutionError until resume() succeeds. Network traffic does not wake a paused sailbox; only resume() does. After sleep(), network ingress, exec(), or resume() wakes the sailbox from its latest checkpoint. terminate() is permanent.

We highly recommend calling sb.checkpoint() after any non-deterministic command. Upon a node failure, Sailboxes will replay commands from the latest checkpoint onwards. Checkpointing immediately after a non-deterministic command allows you to avoid diverging behaviour on replays.

Custom images

Start from the arm64 Debian base image, add build steps, and pass the image definition to Sailbox.create():

image = (
    sail.Image.debian_arm64
    .apt_install("git", "curl")
    .pip_install("requests")
    .run_commands("python3 -m pip show requests >/tmp/requests.txt")
    .env({"APP_ENV": "demo"})
)

sb = sail.Sailbox.create(
    app=app,
    image=image,
    name="custom-image-demo",
    image_build_timeout=1800,
)

Image definitions are immutable; each helper returns a new definition. Supported helpers are apt_install(*packages), pip_install(*packages), run_commands(*commands), env(dict[str, str]), and build(timeout=1800). The SDK retries short-lived imagebuilder socket-close transport races during BuildImage and status polling, but retry sleeps are capped by the same timeout or image_build_timeout budget. Calling build() eagerly builds the image before you create a sailbox. If the same image was already built, the cached image is returned. This step is optional because Sailbox.create() will build custom image definitions before creating the VM.

API surface

Common exported SDK types:

Type Description
sail.App Sail application namespace; use App.find(name=..., mint_if_missing=True)
sail.Image Image namespace with debian_arm64, debian_arm, debian_amd64, and debian_amd
sail.SailFunction Decorated Python function accepted by Sailbox.exec()
sail.Sailbox Standard sailbox handle
sail.SailboxExecRequest Durable exec request returned by exec()
sail.SailboxExecResult stdout, stderr, and returncode from wait()
sail.SailboxListener Public listener metadata for an exposed guest port
sail.SailboxRequest Tracked outbound HTTP request
sail.SailboxResponse HTTP response with status_code, headers, content, text, and json()

Common exceptions:

Exception Raised when
sail.SailError Base SDK error
sail.SailboxError Base sailbox-specific error
sail.SailboxCreationError Creation, checkpoint, stop, start, or scheduler lifecycle operation fails
sail.SailboxExecutionError Exec, listener, or network request operation fails
sail.SailboxExecAlreadyRunningError Another exec request is already active
sail.SailboxExecRequestNotFoundError A durable exec request cannot be found
sail.SailboxFunctionError A decorated Python function raised while running in a sailbox
sail.SailboxFunctionSerializationError Function payload, runtime setup, or return-value serialization fails
sail.SailboxTerminatedError The sailbox no longer exists on the worker
sail.SailboxWorkerLostError The assigned worker was lost during exec wait
sail.ImageBuildError Custom image build fails

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sail_sdk-0.1.12.tar.gz (44.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sail_sdk-0.1.12-py3-none-any.whl (45.1 kB view details)

Uploaded Python 3

File details

Details for the file sail_sdk-0.1.12.tar.gz.

File metadata

  • Download URL: sail_sdk-0.1.12.tar.gz
  • Upload date:
  • Size: 44.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for sail_sdk-0.1.12.tar.gz
Algorithm Hash digest
SHA256 6b8d9cf7c3b566a086ee9ba31c5f8a55c45b7237df7177eae59ca2a2ec0ff2b4
MD5 94b36d090d98172fd783bf8046e304b8
BLAKE2b-256 7978bdfa95bbd14291fea17b02d4ad1900e3c70af1a69eb8b71bf017a63d331c

See more details on using hashes here.

File details

Details for the file sail_sdk-0.1.12-py3-none-any.whl.

File metadata

  • Download URL: sail_sdk-0.1.12-py3-none-any.whl
  • Upload date:
  • Size: 45.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for sail_sdk-0.1.12-py3-none-any.whl
Algorithm Hash digest
SHA256 746ef289378e56396da19897a7e852bb85a87485b26e0b06bc3f406221b125dc
MD5 583953ba6b1977af57e02aa0e04b2440
BLAKE2b-256 6de6bb02e445a24959d306e03cf1b3cfead2abc2f59f7bf462f4da1a46f1195d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page