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.