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 and 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.
Use cases
Section titled “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
dockerdor 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
Section titled “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.
from daytona import Daytona
daytona = Daytona()sandbox = daytona.v2.create("docker-dind")
# Block until Docker Engine answers, up to 120 secondssandbox.docker.wait_until_ready(timeout=120)
response = sandbox.process.run("docker run --rm hello-world")print(response.stdout)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 secondsawait sandbox.docker.waitUntilReady({ timeout: 120_000 })
const response = await sandbox.process.run('docker run --rm hello-world')console.log(response.stdout)daytona sandbox create --image docker-dind
# Block until Docker Engine answers, up to 120 secondsdaytona sandbox docker status {sandboxId} --wait --timeout 120scurl '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/statusuntilstateisready. - See wait for Docker.
Start modes
Section titled “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:
dockerdwith any arguments;dockerd-entrypoint.shwith no arguments, a flag as the first argument, ordockerdas the first argument;docker-entrypoint.shwithdockerdordockerd-entrypoint.shas the first argument. - Daemon search path:
/usr/local/bin,/usr/bin,/usr/sbin,/bin, and/sbin. - Sandbox
PATH: not searched for the daemon.
from daytona import Daytona
daytona = Daytona()sandbox = daytona.v2.create( "my-dind-image", env={"DAYTONA_DOCKER_START": "entrypoint"},)import { Daytona } from '@daytona/sdk'
const daytona = new Daytona()const sandbox = await daytona.v2.create('my-dind-image', { env: { DAYTONA_DOCKER_START: 'entrypoint' },})daytona sandbox create \ --image my-dind-image \ --env DAYTONA_DOCKER_START=entrypointcurl '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
Section titled “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. |
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)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)daytona sandbox docker status {sandboxId}curl 'https://toolbox.daytona.com/toolbox/{sandboxId}/docker/status' \ --header 'Authorization: Bearer YOUR_API_KEY'Wait for Docker
Section titled “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.
from daytona import Daytonafrom 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()raisesDockerNotReadyErrorat once when Docker is failed, not present, disabled, unmanaged, unsupported, or deferred with reasonfunction-ready-template.wait_until_ready()raisesDockerWaitTimeoutErrorwhen Docker is still starting, or deferred with reasongate-closed, at the end of the time budget.DockerWaitTimeoutError.statusholds the last status, orNoneif none arrived.
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()throwsDockerNotReadyErrorat once when Docker is failed, not present, disabled, unmanaged, unsupported, or deferred with reasonfunction-ready-template.waitUntilReady()throwsDockerWaitTimeoutErrorwhen Docker is still starting, or deferred with reasongate-closed, at the end of the time budget.DockerWaitTimeoutError.statusholds the last status, orundefinedif none arrived.
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.
# Hold the response for up to 1000 ms while Docker is starting; repeat until state changescurl '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 treatfailed,not_present,disabled,unmanaged,unsupported, anddeferredwith reasonfunction-ready-templateas final. - The endpoint has no overall time budget. Set a deadline in your client and stop polling when it passes.
Docker logs
Section titled “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.
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}")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) }}daytona sandbox process logs {sandboxId} dockerddockerdaddresses Docker only when the Docker status owner isdaemon.- When the owner is
entrypoint, pass the process ID fromdaytona sandbox docker statusinstead.
curl 'https://toolbox.daytona.com/toolbox/{sandboxId}/processes/dockerd/logs' \ --header 'Authorization: Bearer YOUR_API_KEY'dockerdaddresses Docker only when the Docker statusownerisdaemon.- When the owner is
entrypoint, use theprocessIdfromGET /docker/statusinstead.
Docker storage
Section titled “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
Section titled “Persistence”Because Docker data is part of the sandbox disk, it follows the sandbox’s persistence:
| Operation | Docker data |
|---|---|
| Images and snapshots | Each sandbox created from a captured image or a snapshot 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.