What is Docker-in-Docker (DinD), and what are its risks and alternatives?
Quick Answer
Docker-in-Docker means running a full, separate Docker daemon inside a container — typically needed when a CI pipeline itself runs inside a container but needs to build and run other Docker images as part of its job. It carries real risks (needing to run the outer container in --privileged mode, layered storage-driver complications, and generally weaker isolation than expected), which is why the safer alternatives — mounting the host's Docker socket (with its own distinct risks — see the security topic), or using a purpose-built rootless image-building tool like Kaniko or Buildah — are often preferred instead.
Detailed Answer
Why this scenario comes up at all
Many CI/CD systems run each job inside its own container for isolation and reproducibility — but if that job's own work is to build a Docker image (a very common CI task), you end up needing to run Docker inside the container the CI job itself is running in. This is the scenario Docker-in-Docker addresses.
The DinD approach — a real, nested Docker daemon
docker run --privileged -d --name dind docker:24-dind
docker run --link dind:docker --env DOCKER_HOST=tcp://docker:2375 docker:24 docker build .
This runs an entirely separate Docker daemon inside a container, and a second container talks to that nested daemon to actually perform builds — genuinely running "Docker inside Docker," not just talking to the host's existing daemon.
The real risks of this approach
- Requires
--privilegedmode — running a nested Docker daemon generally requires disabling most container isolation protections for the outer container.--privilegedgrants nearly all capabilities and disables several security restrictions covered in the security topic. This is a significant security relaxation, not a minor detail, and it directly undermines much of the isolation benefit containers are meant to provide in the first place. - Storage-driver complications — running a container filesystem (an overlay/union filesystem; see the fundamentals topic) inside another container's own overlay filesystem has historically caused genuine compatibility and performance issues. This happens because it layers the same kind of filesystem trickery on top of itself.
- Weaker isolation than the "in Docker" framing suggests — despite feeling like it should be "extra isolated" (Docker inside Docker), the
--privilegedrequirement actually means the outer container has less isolation from the host. An ordinary, non-privileged container would have more isolation than this setup provides.
Alternative 1: mounting the host's Docker socket
docker run -v /var/run/docker.sock:/var/run/docker.sock docker:24 docker build .
This avoids running a nested daemon at all — instead, the CI container talks directly to the host's own Docker daemon (see the security topic's Docker-socket question). This avoids DinD's storage-driver and --privileged concerns, but it introduces its own well-documented, serious risk. As covered in that question, socket access is functionally equivalent to host root. Any CI job with this mount has, in effect, host-level access, which is a serious concern for CI systems running untrusted or third-party pipeline code.
Alternative 2: purpose-built rootless image-building tools
# Kaniko, running inside a Kubernetes Pod with no special privileges,
# building an image without ever needing a Docker daemon (nested or host) at all
Tools like Kaniko (built by Google, commonly used in Kubernetes-based CI) and Buildah can build OCI-compliant images without requiring a Docker daemon at all. They implement the image-building logic directly in user space, without needing privileged access or a socket to any daemon. This genuinely avoids both of the above risks, rather than merely mitigating them. This is increasingly the preferred approach specifically for CI systems (especially Kubernetes-based ones) that need to build images as an ordinary, unprivileged step in an otherwise-sandboxed pipeline.
Weighing the tradeoffs
| Approach | Privilege required | Risk profile |
|---|---|---|
| Docker-in-Docker (nested daemon) | --privileged | Significant isolation weakening; storage-driver quirks |
| Mounted host socket | None on the container itself, but socket access = host root | Serious, well-documented escalation risk |
| Kaniko / Buildah (daemonless) | None | Avoids both risks above entirely |
Where DinD or socket-mounting genuinely can't be avoided (some legacy pipeline setups, specific tooling requirements), the right mental model is to treat that CI runner as a fully trusted, high-privilege environment. Restrict what pipelines are allowed to run in it accordingly. Do not treat it as just another routine, low-stakes CI job.