# Docker (/docs/docker)

Docker runs inside a sandbox as a full Docker Engine, so workloads can build images and run containers the same way they do on a regular Linux machine. Daytona does not install Docker. When the sandbox image ships Docker Engine, the sandbox starts it automatically on every boot; when the image does not, nothing starts.

Every Linux sandbox has storage prepared for Docker, with no create-time option. The standard Docker data directories are backed by the sandbox disk, so Docker images, containers, and volumes are stored with the rest of the sandbox filesystem and are kept by [captured images](/docs/images#capture-images) and [snapshots](/docs/snapshots).

Docker startup never blocks the sandbox: the sandbox reports as running as soon as the toolbox is reachable, and Docker reports its own readiness through the Docker status. A Docker failure leaves the sandbox usable, so you can read the logs, fix the configuration, and retry.

<DockerVisual />

## Use cases [#use-cases]

Docker is used when a workload already runs in containers or builds container images.

- **Multi-container applications.** An application with a database, a cache, and its services runs in one sandbox. Docker Engine runs inside the sandbox as on a regular Linux machine. Every Linux sandbox has storage prepared for Docker, with no create-time option.

- **Image builds.** CI jobs and agents build container images and test them before a push. Image layers are stored on the sandbox disk, with no separate Docker allowance. A larger disk size at create time stores more images.

- **Integration tests.** Tests start databases and other dependencies as containers and remove them after the run. Docker starts after the sandbox is running, so a test [waits for Docker](#wait-for-docker) before it starts containers. The wait fails at once when Docker is failed, not present, disabled, unmanaged, or unsupported.

- **Agents that run containers.** An agent pulls and runs containers inside the sandbox, with no access to a Docker daemon on your machines. The sandbox image must ship Docker Engine, because Daytona does not install it. When the image has only the Docker CLI, the Docker status reports not present.

- **Prepared environments.** Images and containers pulled once are kept by a captured image or a snapshot. The next sandbox does not pull them again. Each sandbox created from the captured image or snapshot gets its own copy of the Docker images, containers, and volumes.

## Start Docker [#start-docker]

Start Docker by creating a sandbox from an image that ships Docker Engine. Docker starts in the background after the sandbox is running, so wait for Docker to become ready before you run Docker commands; a command sent earlier can fail because Docker is not answering yet.

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

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create("docker-dind")

# Block until Docker Engine answers, up to 120 seconds
sandbox.docker.wait_until_ready(timeout=120)

response = sandbox.process.run("docker run --rm hello-world")
print(response.stdout)
```

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

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

const daytona = new Daytona()
const sandbox = await daytona.v2.create('docker-dind')

// Block until Docker Engine answers, up to 120 seconds
await sandbox.docker.waitUntilReady({ timeoutSeconds: 120 })

const response = await sandbox.process.run('docker run --rm hello-world')
console.log(response.stdout)
```

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

```bash
daytona sandbox create --image docker-dind

# Block until Docker Engine answers, up to 120 seconds
daytona sandbox docker status {sandboxId} --wait --timeout 120s
```

</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 '{"image": "docker-dind"}'
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- Poll `GET /docker/status` on the toolbox until `state` is `ready`.
</Callout>

</TabItem>
</Tabs>

### Start modes [#start-modes]

Choose how Docker starts with a sandbox environment variable, set when you create the sandbox. The sandbox reads it once per boot. The value is case-insensitive, and an unset, empty, or unknown value selects the default automatic mode.

In automatic mode, the sandbox first checks the image entrypoint. A recognized Docker entrypoint, which is the Docker Engine daemon itself or the official Docker-in-Docker entrypoint script when it runs the daemon, starts Docker, and the sandbox only waits for it. Otherwise, if the image has the Docker Engine daemon in a standard binary directory, the sandbox starts it as root, waits for it, and then starts the image entrypoint. Recognition is narrow: an entrypoint that starts the daemon another way, such as through a shell command or a process supervisor, is not recognized. The sandbox then starts its own daemon first, and the one the entrypoint starts typically fails against it. Use the entrypoint mode for such images.

- **Recognized entrypoints**: `dockerd` with any arguments; `dockerd-entrypoint.sh` with no arguments, a flag as the first argument, or `dockerd` as the first argument; `docker-entrypoint.sh` with `dockerd` or `dockerd-entrypoint.sh` as the first argument.
- **Daemon search path**: `/usr/local/bin`, `/usr/bin`, `/usr/sbin`, `/bin`, and `/sbin`.
- **Sandbox `PATH`**: not searched for the daemon.

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

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create(
    "my-dind-image",
    env={"DAYTONA_DOCKER_START": "entrypoint"},
)
```

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

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

const daytona = new Daytona()
const sandbox = await daytona.v2.create('my-dind-image', {
  env: { DAYTONA_DOCKER_START: 'entrypoint' },
})
```

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

```bash
daytona sandbox create \
  --image my-dind-image \
  --env DAYTONA_DOCKER_START=entrypoint
```

</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 '{
  "image": "my-dind-image",
  "env": { "DAYTONA_DOCKER_START": "entrypoint" }
}'
```

</TabItem>
</Tabs>

| **Value**            | **Behavior**                                                                                                                                                                                                                 |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `auto` (default) |                                                                                                                                                                                                                              |
| `entrypoint`     | The image entrypoint starts Docker. The sandbox only waits for the socket and never starts `dockerd` itself.                                                                                                                 |
| `off`            | The sandbox neither starts nor waits for Docker, and the state is `disabled`. The image entrypoint still runs, so a recognized Docker entrypoint still starts Docker; the state is then `unmanaged` with owner `entrypoint`. |

## Get Docker status [#get-docker-status]

Get the Docker status. The status describes Docker in the current boot and is returned immediately. Reading it never starts or restarts Docker.

| **Field**                                | **Description**                                                                             |
| ---------------------------------------- | ------------------------------------------------------------------------------------------- |
| `state`                              | Where Docker is in its startup. See the table below.                                        |
| `owner`                              | Who starts Docker: `daemon` (the sandbox), `entrypoint` (the image), or `none`. |
| `reason`                             | Why the state is what it is, such as a failure cause; `null` when there is nothing to add.  |
| `processId` (`process_id` in Python) | The process with Docker's output, or `null`.                                                |

| **State**             | **Meaning**                                                                                                                                                              |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`ready`**           | The Docker socket answers. Docker commands work.                                                                                                                         |
| **`starting`**        | Docker is being started or waited for.                                                                                                                                   |
| **`deferred`**        | Docker startup has not run yet in this boot (reason `gate-closed`), or it is skipped for the whole boot (reason `function-ready-template`).                              |
| **`failed`**          | Docker did not come up (start failure, early exit, or the 30-second timeout), or it exited later (reason `exited`). The sandbox stays usable.                             |
| **`not_present`**     | The image has no `dockerd`. The reason is `cli-only` when the image has only the `docker` CLI.                                                                           |
| **`disabled`**        | `DAYTONA_DOCKER_START` is `off`.                                                                                                                                         |
| **`unmanaged`**       | `DOCKER_HOST` points to a non-local engine, or `DAYTONA_DOCKER_START` is `off` and a recognized Docker entrypoint starts Docker. The sandbox does not check that engine. |
| **`unsupported`**     | Docker storage could not be prepared, for example because the image ships a non-empty `/var/lib/docker`, or the sandbox type has no Docker support.                    |

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

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create("docker-dind")

status = sandbox.docker.status()
print(status.state, status.owner, status.reason, status.process_id)
```

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

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

const daytona = new Daytona()
const sandbox = await daytona.v2.create('docker-dind')

const status = await sandbox.docker.status()
console.log(status.state, status.owner, status.reason, status.processId)
```

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

```bash
daytona sandbox docker status {sandboxId}
```

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/docker/status' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- `{toolboxProxyUrl}` is the `toolbox_proxy_url` field of the [sandbox](/docs/sandboxes#get-a-sandbox).
</Callout>

</TabItem>
</Tabs>

## Wait for Docker [#wait-for-docker]

Wait for Docker to become ready. The wait polls the Docker status and returns as soon as Docker is ready. It fails at once when Docker is failed, not present, disabled, unmanaged, or unsupported, and fails with a timeout when Docker is still not ready at the end of the time budget.

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

```python
from daytona import Daytona
from daytona.v2 import DockerNotReadyError, DockerWaitTimeoutError

daytona = Daytona()
sandbox = daytona.v2.create("docker-dind")

try:
    status = sandbox.docker.wait_until_ready(timeout=120)
except DockerNotReadyError as e:
    print("Docker cannot start:", e.status.state, e.status.reason)
except DockerWaitTimeoutError as e:
    print("Docker is still starting:", e.status)
```

| **Parameter** | **Type** | **Description**                                      |
| ------------- | -------- | ---------------------------------------------------- |
| `timeout` | float | Sets how many seconds the wait lasts. `0` waits without a deadline. The default is `60`. Do not pass `0` when Docker is deferred with reason `function-ready-template`, which stays deferred for the whole boot. |

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

```typescript
import { Daytona } from '@daytona/sdk'
import { DockerNotReadyError, DockerWaitTimeoutError } from '@daytona/sdk/v2'

const daytona = new Daytona()
const sandbox = await daytona.v2.create('docker-dind')

try {
  const status = await sandbox.docker.waitUntilReady({ timeoutSeconds: 120 })
} catch (error) {
  if (error instanceof DockerNotReadyError) {
    console.log('Docker cannot start:', error.status.state, error.status.reason)
  } else if (error instanceof DockerWaitTimeoutError) {
    console.log('Docker is still starting:', error.status)
  } else {
    throw error
  }
}
```

| **Parameter** | **Type** | **Description**                                              |
| ------------- | -------- | ------------------------------------------------------------ |
| `timeoutSeconds` | number | Sets how many seconds the wait lasts. `0` waits without a deadline. The default is `60`. Do not pass `0` when Docker is deferred with reason `function-ready-template`, which stays deferred for the whole boot. |

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

```bash
daytona sandbox docker status {sandboxId} --wait --timeout 120s
```

| **Flag**    | **Type** | **Description**                                                              |
| ----------- | -------- | ---------------------------------------------------------------------------- |
| `--timeout` | string | Sets how long `--wait` waits, as a duration such as `120s` or `2m`. The default is `60s`. |

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

```bash
# Hold the response for up to 1000 ms while Docker is starting; repeat until state changes
curl '{toolboxProxyUrl}/{sandboxId}/docker/status?waitMs=1000' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

| **Field**  | **Type**  | **Description**                                                                                                                                                  |
| ---------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `waitMs` | integer | Sets how many milliseconds the response is held. The response is held only while `state` is `starting`. Any other state, including `deferred`, is returned immediately, so space repeated requests yourself. The value is capped at `1000`. |

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- Stop polling at `ready`, and treat `failed`, `not_present`, `disabled`, `unmanaged`, `unsupported`, and `deferred` with reason `function-ready-template` as final.
- The endpoint has no overall time budget, so set a deadline in your client and stop polling when it passes.
</Callout>

</TabItem>
</Tabs>

## Docker logs [#docker-logs]

Read Docker's output. Docker's output is kept in the logs of the process that runs Docker, and the Docker status names that process. When the sandbox starts Docker itself, the process has the fixed name used in the examples below, and its logs stay readable after Docker exits. When the image entrypoint starts Docker, Docker's output is in the entrypoint's logs.

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

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create("docker-dind")

status = sandbox.docker.status()
if status.process_id:
    handle = sandbox.process.connect(status.process_id)
    for frame in handle.logs().items:
        print(f"[{frame.channel}]: {frame.data}")
```

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

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

const daytona = new Daytona()
const sandbox = await daytona.v2.create('docker-dind')

const status = await sandbox.docker.status()
if (status.processId) {
  const handle = sandbox.process.connect(status.processId)
  for (const frame of (await handle.logs()).items) {
    console.log(`[${frame.channel}]:`, frame.data)
  }
}
```

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

```bash
daytona sandbox process logs {sandboxId} dockerd
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- `dockerd` addresses Docker only when the Docker status owner is `daemon`.
- When the owner is `entrypoint`, pass the process ID from `daytona sandbox docker status` instead.
</Callout>

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

```bash
curl '{toolboxProxyUrl}/{sandboxId}/processes/dockerd/logs' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

<Callout type="note" title="Notes">
- The request requires the `write:sandboxes` permission.
- `dockerd` addresses Docker only when the Docker status `owner` is `daemon`.
- When the owner is `entrypoint`, use the `processId` from `GET /docker/status` instead.
</Callout>

</TabItem>
</Tabs>

## Docker storage [#docker-storage]

Docker data lives on the sandbox disk, at the standard paths for rootful Docker:

| **Path**                  | **Contents**                                                    |
| ------------------------- | --------------------------------------------------------------- |
| **`/var/lib/docker`**     | Docker's image layers, containers, and local volumes.           |
| **`/var/lib/containerd`** | containerd's image content, used by Docker Engine 29 and later. |

Docker shares the sandbox disk with everything else in the sandbox; there is no separate Docker allowance. To store more images or containers, raise the sandbox disk size at create time.

If either of these paths is not empty in the image, or is a symbolic link, the contents are left untouched, Docker storage is not prepared, and the Docker storage state reports it as unsupported. Images that preload Docker data this way are not supported.

### Persistence [#persistence]

Because Docker data is part of the sandbox disk, it follows the sandbox's [persistence](/docs/persistence):

| **Operation**                 | **Docker data**                                                                                                                                                                      |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Images and snapshots**      | Each sandbox created from a [captured image](/docs/images#capture-images) or a [snapshot](/docs/snapshots#restore-snapshots) gets its own copy of the source's Docker images, containers, and volumes, and can create new ones. |

A boot from a captured image is a reboot: running containers come back stopped and restart per their Docker restart policy. To resume running containers with their processes intact, use a snapshot.

## Permissions [#permissions]

Docker access runs through the toolbox of a sandbox. Access is governed by one project permission, which you can grant to an [organization role](/docs/organizations#roles) or an [API key](/docs/api-keys).

A [sandbox access token](/docs/sandboxes#create-an-access-token) with the toolbox operation grants the same access to a single sandbox, without an API key. The sandbox read permission alone does not grant toolbox access.

| **Permission**        | **Grants**                                      |
| --------------------- | ----------------------------------------------- |
| **`write:sandboxes`** | Start Docker, get its status, and read its logs |
