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_PROXYandHTTPS_PROXYare 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.
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:
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.
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).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.
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 appAuthorization 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.