Sandboxes

Networking & egress

Every sandbox runs with one of three egress policies, chosen with the network argument at create time. The default is off - no network at all.

  • off (default) - no network device. The sandbox cannot reach anything. Maximum isolation; the only mode that can be paused and resumed.
  • restricted - egress only through a forward proxy that allows a fixed set of hosts over HTTPS.HTTP_PROXY and HTTPS_PROXY are already set inside the sandbox, so proxy-aware tools (pip, npm, curl, standard HTTP libraries) work as-is. Direct DNS and raw sockets do not resolve.
  • open - unrestricted egress. Use only when you trust the code or genuinely need arbitrary network access.

The default restricted allowlist

On restricted, a sandbox can reach a curated set of hosts that cover the things agent code usually needs - package registries, source hosting and the major model APIs:

  • Python packages - pypi.org, pythonhosted.org
  • npm - registry.npmjs.org
  • Debian apt - deb.debian.org, security.debian.org
  • Source + releases - github.com, githubusercontent.com
  • Model APIs - OpenAI, Anthropic, Google, Mistral, Cohere, Groq, and Hugging Face

Each entry also matches its subdomains, and only HTTPS (port 443) is allowed. The live list is returned as default_egress_domains from GET /v1/sandboxes/limits (and Sandbox.limits() in the SDKs).

A custom egress allowlist

When your code needs a host the default set does not cover - an internal API, a private package mirror, a vendor endpoint - declare your own allowlist instead of opening the sandbox up to the whole internet. It is a tier strictly between restricted and open: still HTTPS-only, still proxy-mediated, still no raw sockets or DNS - just scoped to the domains you name.

A custom list replaces the default - it does not add to it
When you pass allow_domains, those domains become the entire allowlist for that sandbox. Include any registries you still need (e.g. pypi.org if you pip install). Start from default_egress_domains and edit from there. Custom allowlists are available on every plan.

Pass it per sandbox at create time:

custom-allowlist
from orkestr import Sandbox

# Restricted egress, but reach your own hosts instead of the default set.
sbx = Sandbox.create(
    template="python-3.12",
    network="restricted",
    allow_domains=["pypi.org", "pythonhosted.org", "api.internal.acme.com"],
)

# See the default set to start from:
print(Sandbox.limits().default_egress_domains)

Entries are bare hostnames - no scheme, port, path or wildcard.api.internal.acme.com is matched along with its subdomains. The list only takes effect on restricted; it is ignored for off and open.

Baking a default into a template

You can also bake an allowlist into a custom template. Every restricted sandbox booted from that template inherits the list, so a team can standardize the reachable hosts once. A create call's own allow_domains still wins over the baked default when both are present.

template with a baked allowlist
curl -X POST https://api.orkestr.eu/v1/templates \
  -H "Authorization: Bearer $ORKESTR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "acme-agent",
    "base_template": "python-3.12",
    "recipe": ["pip install httpx"],
    "network": "restricted",
    "allow_domains": ["pypi.org", "pythonhosted.org", "api.internal.acme.com"]
  }'

# Every restricted sandbox booted from this template inherits the list,
# unless the create call passes its own allow_domains (which wins).
Resolution order
For a restricted sandbox the effective allowlist is, in order: the create call's allow_domains if given, else the template's baked list if it has one, else the platform default set.

Public preview URLs

Egress is about what a sandbox can reach out to. Preview URLs are the other direction: exposing an HTTP port a process inside the sandbox is serving at a public URL. Open it in a browser or embed it in your app and you get a live preview of a dev server running inside the sandbox.

Call sbx.get_host(port) (or GET /v1/sandboxes/{id}/host?port=N) and you get back a hostname of the form <port>-<id>.sbx.orkestr.run, where <id> is the sandbox's public id with the sbx_ prefix dropped and lowercased. A sandbox sbx_01HXYZ... serving on port 3000 is reachable at https://3000-01hxyz....sbx.orkestr.run.

preview-url
from orkestr import Sandbox

# A networked sandbox is required - "off" has no interface to expose.
sbx = Sandbox.create(template="node-22", network="open")

# Start a server inside the sandbox (here, in the background):
sbx.exec("nohup python3 -m http.server 3000 >/tmp/srv.log 2>&1 &")

host = sbx.get_host(3000)     # "3000-01hxyz....sbx.orkestr.run"
url = f"https://{host}"       # open it in a browser or embed it in your app
The URL is public
The preview URL carries no auth - an embedded preview cannot send an Authorization header, so its only capability is the unguessable sandbox id baked into the hostname. Treat it like a secret link: anyone who has it reaches the port while the sandbox is running. The host is stable for the sandbox's lifetime, but serves traffic only while the sandbox is running - a paused or terminated sandbox returns nothing.

Two requirements: the sandbox must be created with network="restricted" or network="open" (an off sandbox has no network interface and no port to expose), and exposing a public port needs a card on file - the free tier cannot. WebSockets ride through, so a dev server's hot-reload (HMR) works through the preview URL. For building these links in a UI, the sandbox GET and list payloads also carry preview_host_base - the port-less <id>.sbx.orkestr.run suffix, or null when the sandbox cannot expose a port.