Skip to main content
Sailboxes are closed to inbound traffic by default. To publish a service, pass the guest port in ingress_ports when you create the Sailbox, start a process that listens on that port, and wait for the listener endpoint to become ready. Sail supports two inbound protocols:
  • HTTP, which returns a public HTTPS URL and supports HTTP and WebSocket traffic.
  • Raw TCP, which returns a public host and port for protocols such as SSH, Postgres, or custom TCP servers.
Ports are attached to the Sailbox lifetime. Terminating the Sailbox removes its listeners.

HTTP and WebSocket services

Pass a bare port number in ingress_ports to expose it over HTTP. The listener resolves to an HttpEndpoint with a routable HTTPS url.
import sail

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

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

sb.exec("mkdir -p /srv/app && echo hello > /srv/app/index.html").wait()
sb.exec("python3 -m http.server 3000", background=True, cwd="/srv/app").wait()

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

print(listener.endpoint.url)
Use the same URL for WebSocket clients by replacing https:// with wss:// when the process inside the Sailbox speaks WebSocket on that port.
ws_url = listener.endpoint.url.replace("https://", "wss://")

Raw TCP ports

Pass sail.IngressPort(port, "tcp") to expose a port as raw TCP instead of HTTP. Use raw TCP when the protocol is not HTTP-aware or the service already implements its own authentication. The listener resolves to a TcpEndpoint with a host and port that any TCP client can dial directly.
sb = sail.Sailbox.create(
    app=app,
    image=sail.Image.debian_arm64,
    name="tcp-demo",
    ingress_ports=[sail.IngressPort(5432, "tcp", cidr_allowlist=["203.0.113.0/24"])],
)
After the service starts, wait for the endpoint and connect with the matching client:
listener = sb.listener(5432).wait(timeout=60)
host, port = listener.endpoint.host, listener.endpoint.port
print(f"postgres://user:password@{host}:{port}/postgres")

SSH access

By default, Sailboxes do not contain an SSH server. You can enable SSH access to your Sailbox by specifying ssh=True when creating the sandbox. This installs an SSH server, exposes guest port 22, adds your public key, and starts the SSH daemon.
import sail

sb = sail.Sailbox.create(
    app=app,
    image=sail.Image.debian_arm64,
    name="ssh-demo",
    ssh=True,
)

endpoint = sb.listener(22).wait(timeout=60).endpoint
print(f"ssh -p {endpoint.port} root@{endpoint.host}")
ssh=True installs your local ~/.ssh/id_ed25519.pub key, falling back to ~/.ssh/id_rsa.pub if needed. Pass ssh="<path-or-key>" to choose a different public key. The listener endpoint gives the host and port to connect with your own ssh client, scp, SSH config, or IDE. From the CLI, sail box create --enable-ssh runs the same setup and prints the SSH command. The SSH server persists across sleep and resume. An open SSH session drops when the Sailbox sleeps, but reconnecting with plain ssh wakes it and uses the same host key. If sshd stops inside a running Sailbox, call sb.start_sshd() again; this operation is idempotent.

Inspect endpoints

Use listener(port) when you know the guest port, or listeners() to list all published ports for a Sailbox:
for listener in sb.listeners():
    listener = listener.wait(timeout=60)
    if listener.protocol == "http":
        print(listener.port, listener.endpoint.url)
    else:
        endpoint = listener.endpoint
        print(listener.port, f"{endpoint.host}:{endpoint.port}")
HTTP listeners expose listener.endpoint.url. TCP listeners expose listener.endpoint.host and listener.endpoint.port.

Access controls

A raw-TCP port is reachable from the public internet with no platform-side authentication. The in-guest daemon, such as sshd, is the only access control, so make sure it requires credentials.
To restrict which source IPs may connect, pass cidr_allowlist:
ingress_ports=[
    sail.IngressPort(22, "tcp", cidr_allowlist=["203.0.113.0/24", "198.51.100.7/32"]),
]
Connections from outside the listed CIDR prefixes are dropped before reaching the guest. An empty or omitted cidr_allowlist means any source may connect. cidr_allowlist is a TCP-only control; it is not accepted on HTTP ports. Exposing a well-known unauthenticated service port, such as Postgres, MySQL, or Redis, as raw TCP with neither a cidr_allowlist nor allow_public=True is rejected. Set a cidr_allowlist, or pass allow_public=True to confirm you want it publicly reachable:
ingress_ports=[
    sail.IngressPort(5432, "tcp", allow_public=True),
]

Sleeping services

Sleeping Sailboxes wake on network ingress. If you call sleep() on a Sailbox with exposed listeners, the next inbound HTTP, WebSocket, or TCP connection wakes the VM before forwarding traffic to the guest process.
sb.sleep()
# A later request to listener.endpoint.url wakes the Sailbox.
Use pause() instead when you want to preserve VM state without waking on network traffic.

Ports and cleanup

Ports must be unique within a Sailbox and between 1 and 65535. Port 10000 is reserved by the platform. Port 22 is reserved for HTTP ingress but is available for raw TCP ingress. Each org can hold a limited number (32) of concurrent raw-TCP endpoints. Since listeners are created with the Sailbox, terminate Sailboxes that no longer need public TCP endpoints, or contact us to raise your limit.