DaytonaDocs
Working with Sandboxes

Port Forwarding

Expose services running inside a sandbox.

Sandboxes are isolated by default: a service listening inside one is not reachable from the internet until you expose it. Daytona gives you three ways to do that, each suited to a different job. Declared ports set up raw TCP/UDP forwarding at create time; preview endpoints give any in-sandbox HTTP port a stable URL — public or token-protected — that you can create and revoke at any time; and SSH tunnels cover quick, private, ad hoc access.

Declaring ports at create

If you know a sandbox will serve traffic, declare its ports when you create it. Daytona assigns a public mapping for each declared port and returns it in the sandbox's port_map field.

Python
from rlp import CreateSandboxFromImageParams, Daytona, PortRequest
 
daytona = Daytona()
sandbox = daytona.create(CreateSandboxFromImageParams(
    image="python:3.12-slim",
    ports=[PortRequest(vm_port=8080, proto="tcp")],
))
TypeScript
import { Daytona } from '@rlp/sdk'
 
const daytona = new Daytona()
const sandbox = await daytona.create({
  image: 'python:3.12-slim',
  ports: [{ vmPort: 8080, proto: 'tcp' }],
})
cURL
curl -X POST https://api.rl.trydaytona.com/vms \
  -H "Authorization: Bearer $RLP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "python:3.12-slim",
    "ports": [{ "vm_port": 8080, "proto": "tcp" }]
  }'
 
# Read the assigned mapping from the sandbox DTO:
curl https://api.rl.trydaytona.com/vms/$SANDBOX_ID \
  -H "Authorization: Bearer $RLP_API_KEY" | jq .port_map

You choose the in-sandbox port (vm_port) and protocol; the platform chooses the public side of the mapping. Fetch the sandbox (GET /vms/:id) and read port_map to learn where to connect.

Preview endpoints

Preview endpoints expose an in-sandbox HTTP port on a stable URL of the form https://<port>-<sandbox-id>.<preview-domain>/, without any create-time declaration. Each endpoint is either private (the default — a credential is required) or public (anyone with the URL can reach it), and you can flip that flag at any time.

The default is fail-closed: a port nobody exposed is not reachable at all, and a freshly exposed one requires a credential unless you explicitly opt into public.

Exposing a port

Python
# Private by default; pass public=True for an unauthenticated URL.
endpoint = sandbox.expose_port(3000)
print(endpoint.url, endpoint.public)   # https://3000-<id>.<domain>/  False
TypeScript
const endpoint = await sandbox.exposePort(3000)
console.log(endpoint.url, endpoint.public)
cURL
curl -X POST https://api.rl.trydaytona.com/vms/$SANDBOX_ID/endpoints \
  -H "Authorization: Bearer $RLP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"port": 3000, "public": false}'

expose_port / exposePort is idempotent: exposing an already-exposed port returns the existing endpoint, adjusting its visibility if it differs from what you asked for. The raw REST call instead returns 409 Conflict on a duplicate.

Accessing a private endpoint

get_preview_link / getPreviewLink returns the endpoint's URL together with a preview token. Send the token in the x-daytona-preview-token header (a project API key or the sandbox's access token also works). The first call registers the port as private if it wasn't exposed yet; repeated calls return the same URL and token.

Python
import requests
 
preview = sandbox.get_preview_link(3000)
resp = requests.get(preview.url,
                    headers={"x-daytona-preview-token": preview.token})
TypeScript
const preview = await sandbox.getPreviewLink(3000)
const res = await fetch(preview.url, {
  headers: { 'x-daytona-preview-token': preview.token },
})

Managing endpoints

Python
sandbox.list_preview_endpoints()          # all endpoints on this sandbox
sandbox.set_preview_public(3000, True)    # make it public (takes effect within ~1s)
sandbox.unexpose_port(3000)               # stop exposing (idempotent)
cURL
# List
curl https://api.rl.trydaytona.com/vms/$SANDBOX_ID/endpoints \
  -H "Authorization: Bearer $RLP_API_KEY"
 
# Flip visibility
curl -X PATCH https://api.rl.trydaytona.com/vms/$SANDBOX_ID/endpoints/$ENDPOINT_ID \
  -H "Authorization: Bearer $RLP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"public": true}'
 
# Remove
curl -X DELETE https://api.rl.trydaytona.com/vms/$SANDBOX_ID/endpoints/$ENDPOINT_ID \
  -H "Authorization: Bearer $RLP_API_KEY"

Endpoints also appear in the Preview tab of the sandbox page in the dashboard.

End-to-end example

Run a web server inside a sandbox and reach it from outside:

Python
from rlp import SessionExecuteRequest
import requests
 
sandbox.process.create_session("web")
sandbox.process.execute_session_command(
    "web", SessionExecuteRequest(command="python -m http.server 3000", run_async=True))
 
preview = sandbox.get_preview_link(3000)
resp = requests.get(preview.url,
                    headers={"x-daytona-preview-token": preview.token})
print(resp.status_code)

Ad hoc access: SSH tunnels

For quick, private access during development — no endpoint, no declared port — tunnel over SSH:

cURL
ssh -p 2222 -L 8000:localhost:8000 "<sandbox_id>#<api_key>"@<ssh-host>

See also

On this page