# Persistence (/docs/persistence)

The writable filesystem of a Daytona sandbox exists only while the sandbox does. Deleting a sandbox discards its filesystem, memory, and processes. To keep state beyond the sandbox, capture it as an image or a snapshot, or write it to storage outside the sandbox.

Snapshots on this page are Linux snapshots: they save memory and disk and restore on the runner that captured them, or on another runner of the region with the same CPU model and hypervisor version. A persistent macOS sandbox keeps its disk across stop and start, and a macOS snapshot saves only the disk of a stopped persistent sandbox, as an image that restores on any Mac of the region. See [macOS filesystem persistence](/docs/macos-sandboxes#filesystem-persistence).

<PersistenceVisual />
| **Layer**            | **What is preserved**                          | **Mechanisms**                                                                  |
| -------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------- |
| **Filesystem**       | Files, packages, repositories, build artifacts | • [**Image capture**](/docs/images#capture-images)                        |
| **Memory**           | Processes, connections, states                 | • [**Snapshots**](/docs/snapshots#create-snapshots)                       |
| **External storage** | Data that outlives any single sandbox          | • [**Volumes**](/docs/volumes)                                            |

## Use cases [#use-cases]

Persistence is used when the state of a sandbox must survive past the sandbox itself.

- **Prepared environments.** An image capture keeps installed packages, repositories, and configuration. New sandboxes start from the image with nothing to reinstall or rebuild. Capture works on a running Linux or Windows sandbox, which keeps running during the capture.

- **Runs from a known state.** A snapshot copies a configured sandbox, so several runs or every agent in a fleet start from the same state. A restored sandbox continues with its processes still running. A snapshot restores in its home region, or, where Daytona has enabled cross-region restore, in another region if the source sandbox was not persistent.

- **Servers that resume without a restart.** A snapshot taken after a server starts keeps the server in memory. A restored sandbox continues with the server already running, instead of starting it again after a clean boot. The restored sandbox gets a fresh IP and port mappings.

- **Results that outlive the sandbox.** A sandbox writes its output to storage outside the sandbox, such as your own object storage, and the data stays after the sandbox is deleted. Data on a [volume](/docs/volumes) also stays, and a later sandbox can attach the same volume and read it. A disk volume mounts as a local filesystem, while a bucket volume writes straight to an S3 bucket.

- **Work that survives timeouts and reclaims.** A [timeout](#retention-and-lifecycle) or a spot reclaim can delete a sandbox without a request from you, and a host failure loses an ephemeral sandbox, along with its state. Results on a volume or in a captured image stay when that happens. A persistent macOS sandbox stops on a timeout instead of being deleted, and keeps its disk.

## Filesystem persistence [#filesystem-persistence]

Filesystem persistence keeps the contents of a sandbox's disk: files, installed packages, cloned repositories, and build artifacts. Delete discards a sandbox's filesystem, and a host failure loses an ephemeral sandbox. Capture an [**image**](/docs/images#capture-images) or write results to storage outside the sandbox to keep them.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

# Prepare the environment to capture
sandbox.process.run("pip install requests")

# An image capture keeps the filesystem beyond the sandbox
image = sandbox.capture_image(name="my-env")

# Capture runs asynchronously; wait until the image is ready
while image.status not in ("ready", "failed"):
    image = daytona.v2.images.get(image.id, wait="ready")
if image.status == "failed":
    raise RuntimeError(image.error)

# Delete discards the sandbox; the image remains
sandbox.delete()

# Any number of new sandboxes can start from the same image
clone = daytona.v2.create("my-env")
response = clone.process.run("pip show requests")
print(response.stdout)  # requests is already installed
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

// Prepare the environment to capture
await sandbox.process.run('pip install requests')

// An image capture keeps the filesystem beyond the sandbox
let image = await sandbox.captureImage({ name: 'my-env' })

// Capture runs asynchronously; wait until the image is ready
while (image.status !== 'ready' && image.status !== 'failed') {
  image = await daytona.v2.images.get(image.id, { wait: 'ready' })
}
if (image.status === 'failed') throw new Error(image.error ?? 'image capture failed')

// Delete discards the sandbox; the image remains
await sandbox.delete()

// Any number of new sandboxes can start from the same image
const clone = await daytona.v2.create('my-env')
const response = await clone.process.run('pip show requests')
console.log(response.stdout) // requests is already installed
```

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
daytona sandbox create

# Prepare the environment to capture
daytona sandbox exec {sandboxId} -- pip install requests

# An image capture keeps the filesystem beyond the sandbox; the command waits until the image is ready
daytona image capture {sandboxId} --name my-env

# Delete discards the sandbox; the image remains
daytona sandbox delete {sandboxId} --yes

# Any number of new sandboxes can start from the same image
daytona sandbox create --image my-env
daytona sandbox exec {cloneId} -- pip show requests
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
curl 'https://api.daytona.com/sandboxes' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{}'

# Prepare the environment to capture
curl '{toolboxProxyUrl}/{sandboxId}/processes?waitMs=60000&logs=text' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"kind": "exec", "shellCommand": "pip install requests"}'

# An image capture keeps the filesystem beyond the sandbox
curl 'https://api.daytona.com/sandboxes/{sandboxId}/images' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"name": "my-env"}'

# Capture runs asynchronously; block until the image is ready
curl 'https://api.daytona.com/images/{imageId}?wait=ready' \
  --header 'Authorization: Bearer YOUR_API_KEY'

# Delete discards the sandbox; the image remains
curl 'https://api.daytona.com/sandboxes/{sandboxId}' \
  --request DELETE \
  --header 'Authorization: Bearer YOUR_API_KEY'

# Any number of new sandboxes can start from the same image
curl 'https://api.daytona.com/sandboxes' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"image": "my-env"}'
```

<Callout type="note" title="Notes">
- The requests require the `write:sandboxes`, `write:images`, and `delete:sandboxes` permissions.
- `{toolboxProxyUrl}` is the `toolbox_proxy_url` field of the [sandbox](/docs/sandboxes#get-a-sandbox).
- The wait request returns at `ready` or `failed`, or after 30 seconds.
- Repeat the wait request while the image status is `pending` or `building`. A `failed` image does not become `ready`.
</Callout>

</TabItem>
</Tabs>

## Memory persistence [#memory-persistence]

Memory persistence keeps the runtime state of a sandbox: running processes, open connections, and everything loaded in RAM. A [**snapshot**](/docs/snapshots#create-snapshots) captures the memory and disk of a running persistent Linux sandbox or one served from a warm template, and a sandbox restored from it continues from the point it was captured, with processes still running, instead of starting from a clean boot. macOS snapshots do not save memory.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
# Snapshot capture needs a persistent sandbox or one served from a warm template
sandbox = daytona.v2.create()

# Start a server that runs in the background
sandbox.process.start("python3 -m http.server 8000")

# A snapshot keeps memory and running processes beyond the sandbox
snapshot = sandbox.create_snapshot("my-snapshot")

# Capture runs asynchronously; wait until the snapshot is ready
while snapshot.status not in ("ready", "failed"):
    snapshot = daytona.v2.snapshots.get(snapshot.id, wait="ready")
if snapshot.status == "failed":
    raise RuntimeError(snapshot.error)

# Delete discards the sandbox; the snapshot remains
sandbox.delete()

# The restored sandbox resumes the server without starting it again
restored = daytona.v2.create_from_snapshot("my-snapshot")
response = restored.process.run(argv=[
    "python3", "-c",
    "import urllib.request; print(urllib.request.urlopen('http://localhost:8000').status)",
])
print(response.stdout)  # 200
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
// Snapshot capture needs a persistent sandbox or one served from a warm template
const sandbox = await daytona.v2.create()

// Start a server that runs in the background
await sandbox.process.start('python3 -m http.server 8000')

// A snapshot keeps memory and running processes beyond the sandbox
let snapshot = await sandbox.createSnapshot('my-snapshot')

// Capture runs asynchronously; wait until the snapshot is ready
while (snapshot.status !== 'ready' && snapshot.status !== 'failed') {
  snapshot = await daytona.v2.snapshots.get(snapshot.id, { wait: 'ready' })
}
if (snapshot.status === 'failed') throw new Error(snapshot.error ?? 'snapshot capture failed')

// Delete discards the sandbox; the snapshot remains
await sandbox.delete()

// The restored sandbox resumes the server without starting it again
const restored = await daytona.v2.createFromSnapshot('my-snapshot')
const response = await restored.process.run({
  argv: [
    'python3', '-c',
    "import urllib.request; print(urllib.request.urlopen('http://localhost:8000').status)",
  ],
})
console.log(response.stdout) // 200
```

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
# Snapshot capture needs a persistent sandbox or one served from a warm template
daytona sandbox create

# Start a server that runs in the background
daytona sandbox exec {sandboxId} -- bash -c 'nohup python3 -m http.server 8000 > /tmp/server.log 2>&1 &'

# A snapshot keeps memory and running processes beyond the sandbox; the command waits until the snapshot is ready
daytona snapshot create {sandboxId} --name my-snapshot

# Delete discards the sandbox; the snapshot remains
daytona sandbox delete {sandboxId} --yes

# The restored sandbox resumes the server without starting it again
daytona sandbox create-from-snapshot my-snapshot
daytona sandbox exec {restoredId} -- python3 -c "import urllib.request; print(urllib.request.urlopen('http://localhost:8000').status)"
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Snapshot capture needs a persistent sandbox or one served from a warm template
curl 'https://api.daytona.com/sandboxes' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{}'

# Start a server that runs in the background
curl '{toolboxProxyUrl}/{sandboxId}/processes' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"kind": "exec", "shellCommand": "python3 -m http.server 8000"}'

# A snapshot keeps memory and running processes beyond the sandbox
curl 'https://api.daytona.com/sandboxes/{sandboxId}/snapshots' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"name": "my-snapshot"}'

# Capture runs asynchronously; block until the snapshot is ready
curl 'https://api.daytona.com/snapshots/{snapshotId}?wait=ready&timeout_ms=60000' \
  --header 'Authorization: Bearer YOUR_API_KEY'

# Delete discards the sandbox; the snapshot remains
curl 'https://api.daytona.com/sandboxes/{sandboxId}' \
  --request DELETE \
  --header 'Authorization: Bearer YOUR_API_KEY'

# The restored sandbox resumes the server without starting it again
curl 'https://api.daytona.com/sandboxes/from-snapshot' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"snapshot": "my-snapshot"}'

curl '{toolboxProxyUrl}/{restoredId}/processes?waitMs=5000&logs=text' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"kind": "exec", "argv": ["python3", "-c", "import urllib.request; print(urllib.request.urlopen(\"http://localhost:8000\").status)"]}'
```

<Callout type="note" title="Notes">
- The requests require the `write:sandboxes`, `write:snapshots`, and `delete:sandboxes` permissions.
- The wait request returns at `ready` or `failed`, or after `timeout_ms`.
- Repeat the wait request while the snapshot status is `pending` or `building`. A `failed` snapshot does not become `ready`.
</Callout>

</TabItem>
</Tabs>

## Persistence beyond a sandbox [#persistence-beyond-a-sandbox]

Deleting a sandbox deletes its state. To persist state beyond the sandbox itself, capture it as an **image** or a **snapshot**, or write it to storage outside the sandbox.

### Images and snapshots [#images-and-snapshots]

An [image capture](/docs/images#capture-images) saves the filesystem of a running Linux or Windows sandbox under a name, and new sandboxes boot from it. A [snapshot](/docs/snapshots) saves the memory and disk of a running persistent Linux sandbox or one served from a warm template, and a restored sandbox resumes the captured processes. A snapshot restores on the runner that captured it, or on another runner of the region with the same CPU model and hypervisor version. A macOS snapshot is an image of the disk of a stopped persistent sandbox and restores on any Mac of the region ([macOS snapshots](/docs/macos-sandboxes#filesystem-snapshot)).

A snapshot covers the sandbox's own state. A restored sandbox gets a fresh IP and port mappings. Deleting the source sandbox does not delete its snapshots.

### Volumes [#volumes]

A [volume](/docs/volumes) is project-scoped storage that a sandbox attaches when it is created. Data on a volume stays when the sandbox is deleted, and a later sandbox can attach the same volume and read it. A disk volume is mounted as a local filesystem and commits each sandbox's changes in the background; a bucket volume mounts an S3 bucket, managed by Daytona or your own, and writes go straight to the bucket.

## Retention and lifecycle [#retention-and-lifecycle]

When one of a sandbox's [timeouts](/docs/linux-sandboxes#timeouts) elapses, Daytona runs the action set for that timeout: delete, or stop for a [persistent](/docs/macos-sandboxes#lifecycle) macOS sandbox. The timeouts and actions come from the sandbox's lifecycle policy, the project's lifecycle defaults, or the platform default timeout. Two platform events also act on sandboxes without a request from you:

- **Spot reclaim**: when the platform reclaims a [spot](/docs/preemption#spot-sandboxes) sandbox, an ephemeral sandbox is deleted, and a Windows sandbox is stopped with its disk kept. Reclaimed sandboxes record the time of the reclaim. See [preemption](/docs/preemption).
- **Host failure**: an ephemeral sandbox on a failed host is lost.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()

# Create, use, delete
sandbox = daytona.v2.create()

# Run the workload and read the results before deleting
response = sandbox.process.run("python3 -c 'print(1 + 1)'")
print(response.stdout)

# Delete discards the sandbox and all of its state
sandbox.delete()
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

// Create, use, delete
const sandbox = await daytona.v2.create()

// Run the workload and read the results before deleting
const response = await sandbox.process.run("python3 -c 'print(1 + 1)'")
console.log(response.stdout)

// Delete discards the sandbox and all of its state
await sandbox.delete()
```

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
# Create, use, delete
daytona sandbox create

# Run the workload and read the results before deleting
daytona sandbox exec {sandboxId} -- python3 -c 'print(1 + 1)'

# Delete discards the sandbox and all of its state
daytona sandbox delete {sandboxId} --yes
```

</TabItem>
<TabItem label="API" icon="seti:json">

```bash
# Create, use, delete
curl 'https://api.daytona.com/sandboxes' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{}'

# Run the workload and read the results before deleting
curl '{toolboxProxyUrl}/{sandboxId}/processes?waitMs=5000&logs=text' \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --data '{"kind": "exec", "shellCommand": "python3 -c '\''print(1 + 1)'\''"}'

# Delete discards the sandbox and all of its state
curl 'https://api.daytona.com/sandboxes/{sandboxId}' \
  --request DELETE \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

</TabItem>
</Tabs>
