# 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 (`dockerd`), 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 roots `/var/lib/docker` and `/var/lib/containerd` 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](https://www.daytona.io/docs/en/v2/images.md#capture-images) and [snapshots](https://www.daytona.io/docs/en/v2/snapshots.md).

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.

## 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.
- **Image builds**: CI jobs and agents build container images and test them before a push.
- **Integration tests**: tests start databases and other dependencies as containers and remove them after the run.
- **Agents that run containers**: an agent pulls and runs containers inside the sandbox, with no access to a Docker daemon on your machines.
- **Docker-in-Docker images**: an image whose entrypoint is **`dockerd`** or the official Docker-in-Docker entrypoint script works without changes. The sandbox recognizes the entrypoint and waits for Docker to become ready.
- **Prepared environments**: images and containers pulled once are kept by a captured image or a snapshot. The next sandbox does not pull them again.
- **Running containers across restores**: a snapshot keeps running containers with their processes intact.
- **Docker startup troubleshooting**: a Docker failure leaves the sandbox usable. The Docker status and logs show the cause, and Docker is started again after the configuration is fixed.

## 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.


```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)
```


```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({ timeout: 120_000 })

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


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

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


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

- Poll the toolbox **`GET /docker/status`** until **`state`** is **`ready`**.
- See [wait for Docker](#wait-for-docker).


### 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.


```python
from daytona import Daytona

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


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

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


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


```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" }
}'
```


| **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 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.                    |


```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)
```


```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)
```


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


```bash
curl 'https://toolbox.daytona.com/toolbox/{sandboxId}/docker/status' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```


## 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 reaches a state that cannot become ready in the current boot, and fails with a timeout when Docker is still not ready at the end of the time budget.


```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`**. |

- **`wait_until_ready()`** raises **`DockerNotReadyError`** at once when Docker is failed, not present, disabled, unmanaged, unsupported, or deferred with reason **`function-ready-template`**.
- **`wait_until_ready()`** raises **`DockerWaitTimeoutError`** when Docker is still starting, or deferred with reason **`gate-closed`**, at the end of the time budget.
- **`DockerWaitTimeoutError.status`** holds the last status, or **`None`** if none arrived.


```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({ timeout: 120_000 })
} 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**                                              |
| ------------- | -------- | ------------------------------------------------------------ |
| **`timeout`** | **`number`** | Sets how many milliseconds the wait lasts. **`0`** waits without a deadline. The default is **`60000`**. |

- **`waitUntilReady()`** throws **`DockerNotReadyError`** at once when Docker is failed, not present, disabled, unmanaged, unsupported, or deferred with reason **`function-ready-template`**.
- **`waitUntilReady()`** throws **`DockerWaitTimeoutError`** when Docker is still starting, or deferred with reason **`gate-closed`**, at the end of the time budget.
- **`DockerWaitTimeoutError.status`** holds the last status, or **`undefined`** if none arrived.


```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`**. |

- The wait fails at once when Docker is failed, not present, disabled, unmanaged, unsupported, or deferred with reason **`function-ready-template`**.
- The wait fails with a timeout when Docker is still starting, or deferred with reason **`gate-closed`**, at the end of the time budget.


```bash
# Hold the response for up to 1000 ms while Docker is starting; repeat until state changes
curl 'https://toolbox.daytona.com/toolbox/{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`**. |

- The endpoint returns every state as a normal response. 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. Set a deadline in your client and stop polling when it passes.


## 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.


```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}")
```


```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)
  }
}
```


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

- **`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.


```bash
curl 'https://toolbox.daytona.com/toolbox/{sandboxId}/processes/dockerd/logs' \
  --header 'Authorization: Bearer YOUR_API_KEY'
```

- **`dockerd`** addresses Docker only when the Docker status **`owner`** is **`daemon`**.
- When the owner is **`entrypoint`**, use the **`processId`** from **`GET /docker/status`** instead.


## 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 the image already contains a non-empty `/var/lib/docker` or `/var/lib/containerd`, or either path is a symbolic link, the contents are left untouched, Docker storage is not prepared, and the state is `unsupported`. Images that preload Docker data this way are not supported.

### Persistence

Because Docker data is part of the sandbox disk, it follows the sandbox's [persistence](https://www.daytona.io/docs/en/v2/persistence.md):

| **Operation**                 | **Docker data**                                                                                                                                                                      |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Images and snapshots**      | Each sandbox created from a [captured image](https://www.daytona.io/docs/en/v2/images.md#capture-images) or a [snapshot](https://www.daytona.io/docs/en/v2/snapshots.md#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 (`--restart`). To resume running containers with their processes intact, use a snapshot.