---
title: Isolation
description: Sandbox runtime, network, and project isolation.
---
Daytona sandboxes are isolated by default. Each sandbox runs as a hardware-isolated microVM with its own kernel: code running in a sandbox cannot read another sandbox's filesystem or memory, is not on a shared network with other sandboxes, and its credentials and API access are scoped to its own project. Isolation operates at three boundaries:
| **Boundary** | **What is separated** | **Mechanisms** |
| ------------ | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Runtime** | Kernel, processes, filesystem, and memory of each sandbox | • [**Sandboxes**](/docs/v2/sandboxes)
• [**Resources**](/docs/v2/sandboxes#resources) |
| **Network** | Traffic entering (ingress) and leaving (egress) each sandbox | • [**Network policy**](/docs/v2/network-limits)
• [**Preview**](/docs/v2/preview)
• [**Ports**](/docs/v2/linux-sandboxes#create-a-linux-sandbox) |
| **Project** | Access to sandboxes, data, and credentials | • [**Projects**](/docs/v2/projects)
• [**API keys**](/docs/v2/api-keys#permissions--scopes)
• [**Secrets**](/docs/v2/secrets) |
## Runtime isolation
Runtime isolation separates what runs inside one sandbox from the host it executes on and from every other sandbox. Each sandbox runs as a Firecracker microVM with its own kernel, filesystem, and network stack. The boundary is enforced by the hypervisor, not by the guest: code inside a sandbox cannot observe or interfere with other sandboxes or the host.
Resources are part of the runtime boundary. Each sandbox has its own [**vCPU**, **memory**, and **disk**](/docs/v2/sandboxes#resources), and the guest kernel sees only the resources of its own virtual machine. The guest boots with the burst ceiling of the sandbox, so `nproc` and `/proc/meminfo` report that ceiling, not the guaranteed CPU. Tools that size themselves from the CPU count, such as `pytest -n auto` or `make -j$(nproc)`, start one worker per visible CPU.
```bash
nproc # vCPUs of the sandbox's virtual machine
grep MemTotal /proc/meminfo # memory of the sandbox's virtual machine
```
## Network isolation
Network isolation controls traffic in each direction separately. Egress is open by default and locked down per sandbox with a network policy; ingress is closed by default and opened per endpoint; sandboxes never share a network.
| **Direction** | **Default** | **Controls** |
| ----------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Sandbox to internet** | Open egress | [**Network policy**](/docs/v2/network-limits): default-deny with an allow list of IPs, CIDRs, and domains |
| **Internet to sandbox** | Closed; no port is reachable until exposed | [**Preview URLs**](/docs/v2/preview) (token-authenticated unless an endpoint is made public), [**declared ports**](/docs/v2/linux-sandboxes#create-a-linux-sandbox), and [**SSH**](/docs/v2/ssh-access) |
| **Sandbox to sandbox** | No shared network | None; sandboxes reach each other only through exposed endpoints |
**Outbound** traffic is open by default. A [network policy](/docs/v2/network-limits) turns on blocking and admits only the domains and IPv4 ranges on its allow lists. With blocking on and empty allow lists, the sandbox reaches no external address and DNS resolves no names.
```python
from daytona import Daytona
daytona = Daytona()
# Block all outbound traffic
sandbox = daytona.v2.create(network_policy={"block_all": True})
# Or allow specific domains only
sandbox = daytona.v2.create(
network_policy={"allow_domains": ["example.com", "*.daytona.io"]},
)
# Or allow specific IPv4 ranges only
sandbox = daytona.v2.create(
network_policy={"block_all": True, "allow_cidrs": ["208.80.154.232/32", "192.168.1.0/24"]},
)
```
```typescript
import { Daytona } from '@daytona/sdk'
const daytona = new Daytona()
// Block all outbound traffic
const blocked = await daytona.v2.create({
networkPolicy: { blockAll: true },
})
// Or allow specific domains only
const domainRestricted = await daytona.v2.create({
networkPolicy: { allowDomains: ['example.com', '*.daytona.io'] },
})
// Or allow specific IPv4 ranges only
const cidrRestricted = await daytona.v2.create({
networkPolicy: { blockAll: true, allowCidrs: ['208.80.154.232/32', '192.168.1.0/24'] },
})
```
```bash
# Block all outbound traffic
daytona sandbox create --block-all
# Or allow specific domains only
daytona sandbox create \
--allow-domain example.com \
--allow-domain '*.daytona.io'
# Or allow specific IPv4 ranges only
daytona sandbox create \
--block-all \
--allow-cidr 208.80.154.232/32 \
--allow-cidr 192.168.1.0/24
```
```bash
# Block all outbound traffic
curl 'https://api.daytona.com/sandboxes' \
--request POST \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
"network_policy": { "block_all": true }
}'
# Or allow specific domains only
curl 'https://api.daytona.com/sandboxes' \
--request POST \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
"network_policy": { "allow_domains": ["example.com", "*.daytona.io"] }
}'
# Or allow specific IPv4 ranges only
curl 'https://api.daytona.com/sandboxes' \
--request POST \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
"network_policy": { "block_all": true, "allow_cidrs": ["208.80.154.232/32", "192.168.1.0/24"] }
}'
```
**Inbound** traffic reaches a sandbox only through a port exposed as a [preview](/docs/v2/preview) endpoint or through [SSH access](/docs/v2/ssh-access). A port is exposed as a private endpoint when you first request its preview URL, and a private endpoint requires a preview token or an API key. A port made public accepts requests without a credential.
```python
from daytona import Daytona
daytona = Daytona()
sandbox = daytona.v2.create()
# Requesting a preview link exposes the port as a private endpoint
preview = sandbox.ports.preview_link(3000)
print(preview.url) # https://3000-{sandboxId}.{proxy-domain}
print(preview.token) # sent in the x-daytona-preview-token header
# A public endpoint accepts requests without a credential
sandbox.ports.expose(8080, public=True)
```
```typescript
import { Daytona } from '@daytona/sdk'
const daytona = new Daytona()
const sandbox = await daytona.v2.create()
// Requesting a preview link exposes the port as a private endpoint
const preview = await sandbox.ports.previewLink(3000)
console.log(preview.url) // https://3000-{sandboxId}.{proxy-domain}
console.log(preview.token) // sent in the x-daytona-preview-token header
// A public endpoint accepts requests without a credential
await sandbox.ports.expose(8080, { public: true })
```
```bash
# Requesting a preview URL exposes the port as a private endpoint
daytona sandbox preview-url {sandboxId} 3000
```
```bash
# Requesting a preview URL exposes the port as a private endpoint
curl 'https://api.daytona.com/sandboxes/{sandboxId}/ports/3000/preview-url' \
--request POST \
--header 'Authorization: Bearer YOUR_API_KEY'
# Authenticate to the private endpoint with the returned token
curl 'https://3000-{sandboxId}.{proxy-domain}' \
--header 'x-daytona-preview-token: PREVIEW_TOKEN'
# A public endpoint accepts requests without a credential
curl 'https://api.daytona.com/sandboxes/{sandboxId}/ports/8080' \
--request PUT \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{ "public": true }'
```
## Project isolation
Project isolation separates tenants. [Organizations](/docs/v2/organizations) are the unit of membership and billing; [projects](/docs/v2/projects) are the unit of isolation. Every sandbox, image, snapshot, volume, secret, and project API key belongs to exactly one project, and access control is enforced at that boundary: a project API key cannot see or operate on another project's resources. Organization API keys are scoped to the organization.
Within a project, access narrows further to the following mechanisms:
| **Mechanism** | **What it scopes** | **How it works** |
| ------------------------------------------------------------ | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [**Scoped API keys**](/docs/v2/api-keys#permissions--scopes) | What a key can do | Each key carries an explicit `verb:resource` scope list chosen at creation, and every request is confined to those scopes; `write:` and `delete:` imply `read:` on the same resource. Keys can carry an expiry. Revocation takes effect immediately on the API; toolbox and SSH access through the proxy can continue for up to 30 seconds after revocation. |
| [**Secrets**](/docs/v2/secrets) | Credentials used in a sandbox | A sandbox holds an opaque placeholder; an outbound proxy substitutes the real value only for requests to the secret's allowed domains. Responses are not rewritten, so a destination that echoes the credential back returns the real value into the sandbox. |
**[API key permissions](/docs/v2/api-keys#permissions--scopes)** scope what a key can do. A key created with only `write:sandboxes` and `delete:sandboxes` can manage sandboxes but cannot read secrets or delete images. A key can hold only permissions its creator holds.
```bash
# A key limited to creating and deleting sandboxes in one project
daytona key mint --project PROJECT_ID \
--name tenant-a-key \
--permission write:sandboxes,delete:sandboxes \
--expires 720h
```
```bash
# A key limited to creating and deleting sandboxes in one project
curl 'https://api.daytona.com/projects/PROJECT_ID/api-keys' \
--request POST \
--header 'Authorization: Bearer YOUR_OIDC_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"name": "tenant-a-key",
"project_permissions": ["write:sandboxes", "delete:sandboxes"],
"expires_at": "2027-01-01T00:00:00Z"
}'
```
**[Secrets](/docs/v2/secrets)** keep credentials out of sandboxes. A sandbox holds an opaque placeholder; an outbound proxy substitutes the real value only for HTTPS requests to the secret's allowed domains. Code in the sandbox can use the credential but cannot read it directly or send it to another host. Responses are not rewritten, so an allowed destination that echoes the credential back exposes the real value to the sandbox.
```python
from daytona import Daytona
daytona = Daytona()
daytona.v2.secret.set(
"MY_API_KEY",
"secret-value",
allowed_domains=["api.example.com"],
)
# The sandbox receives a placeholder, never the plaintext value
sandbox = daytona.v2.create(secrets=["MY_API_KEY"])
# Code uses the credential without being able to read it
sandbox.process.run(
'curl -H "Authorization: Bearer $MY_API_KEY" https://api.example.com/v1/data'
)
```
```typescript
import { Daytona } from '@daytona/sdk'
const daytona = new Daytona()
await daytona.v2.secret.set('MY_API_KEY', 'secret-value', {
allowedDomains: ['api.example.com'],
})
// The sandbox receives a placeholder, never the plaintext value
const sandbox = await daytona.v2.create({
secrets: ['MY_API_KEY'],
})
// Code uses the credential without being able to read it
await sandbox.process.run(
'curl -H "Authorization: Bearer $MY_API_KEY" https://api.example.com/v1/data'
)
```
```bash
printf %s 'secret-value' | daytona secret set MY_API_KEY --stdin --allowed-domain api.example.com
# The sandbox receives a placeholder, never the plaintext value
daytona sandbox create --secret MY_API_KEY
# Code uses the credential without being able to read it
daytona sandbox exec {sandboxId} -- sh -c \
'curl -H "Authorization: Bearer $MY_API_KEY" https://api.example.com/v1/data'
```
```bash
curl 'https://api.daytona.com/secrets/MY_API_KEY' \
--request PUT \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"value": "secret-value",
"allowed_domains": ["api.example.com"]
}'
# The sandbox receives a placeholder, never the plaintext value
curl 'https://api.daytona.com/sandboxes' \
--request POST \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"secrets": ["MY_API_KEY"]
}'
```