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.
You can also add and remove ports on a running Sailbox with expose/unexpose
(see Add or remove ports at runtime).
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.
Add or remove ports at any time (see below). Terminating the Sailbox removes all
of 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
Every Sailbox image ships with an SSH server installed. Enable SSH access by
specifying ssh=True at create time, or by calling sb.enable_ssh() on any
running Sailbox. Both expose guest port 22, add your public key, and start 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.enable_ssh() 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.
Add or remove ports at runtime
You don’t have to declare every port at create time. expose publishes a new
port on a running Sailbox and unexpose removes one, with no guest restart.
# Add an HTTP port and read its URL.
http = sb.expose(8080)
print(http.wait(timeout=60).endpoint.url)
# Add a CIDR-restricted raw-TCP port, then remove it when you're done.
sb.expose(5432, protocol="tcp", cidr_allowlist=["203.0.113.0/24"])
sb.unexpose(5432)
Re-exposing a port under the same protocol updates its cidr_allowlist to the
value you pass. Use it to tighten or relax a live raw-TCP port. Unexposing a
raw-TCP port stops serving it and frees its endpoint-quota slot, but keeps its
public host:port reserved to your Sailbox. Re-expose the same guest port to
reclaim the exact address, and no other Sailbox ever takes it. A raw-TCP guest
port therefore can’t be repurposed to HTTP; use a different guest port. HTTP
ports carry no such reservation and are released on unexpose.
expose and unexpose work on a paused or sleeping Sailbox without waking it; a
later resume serves the new listener.
From the CLI:
sail box expose <id> 8080
sail box expose <id> 5432 --tcp --cidr 203.0.113.0/24
sail box unexpose <id> 5432
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. Ports 10000,
10001, 15001, and 15002 are 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. The
limit counts actively-exposed endpoints, so unexpose frees a slot. The port’s
host:port stays reserved to your Sailbox but no longer counts. Contact us to
raise your limit if you need more concurrent endpoints. (A reserved host:port
is never reused by another Sailbox, even after unexpose or termination.)