The Magic Behind Docker-in-Docker (DinD)
Docker-in-Docker (DinD) is a popular technique in CI/CD pipelines to enable containerized builds.
But why is DinD necessary?
Does it actually run a Docker container inside another Docker container?
Let’s say you use Gitlab CI (or Jenkins, Circle CI etc.). In your .gitlab-ci.yml file, you define a task like this:
image: ubuntu:25.10
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2375/
DOCKER_TLS_CERTDIR: ""
stages:
- test123
run-tests123:
stage: test123
before_script:
- apt-get update && apt-get install -y docker.io
script:
- docker pull ailhan/web-debug:latest
- docker run ailhan/web-debug:latest ./runtests.shThe problem definition
You want to download the ailhan/web-debug image, run it as a container, and perform some tests.
Forget about the above-mentioned example yaml file.
You run CI jobs in a Docker container or a Kubernetes pod.
You want to get the latest container image from artifactory and run some tests.
When your CI job runs inside a container or Kubernetes pod, it cannot directly launch sibling Docker containers because Docker requires access to the host’s Docker daemon, which is not accessible inside the container by default.
There should be a mechanism that allows you to launch another Docker container on the host machine.
The solution
This is where DinD (Docker-in-Docker) comes into play.
It sounds like we will run a Docker container within a Docker container.
Actually, it’s not exactly running a container fully nested inside another container.
We will run the CI job (run-tests123) inside a special container (docker:dind), which allows you to run another Docker container on the host machine.
DinD doesn’t allow you to run a Docker container fully nested inside another container. Instead, it runs a Docker daemon inside a special container (docker:dind) with elevated privileges.
Your CI job connects to the Docker daemon running inside the DinD container, which acts like a Docker host and lets you launch sibling containers.
DinD allows you to create another container (ubuntu:25.10) on the host machine.
We install the Docker CLI inside the Ubuntu container to communicate with the Docker daemon running inside the DinD container.
Thanks to the environment variable DOCKER_HOST, it will connect to the Docker daemon on the host machine.
After that, we will be able to run ailhan/web-debug container and perform the tests.
What’s the magic behind the Docker-in-Docker container?
It’s actually a simple container. It simply installs Docker and runs the container with the --privleged flag. The --privileged flag grants the container extended privileges, allowing it to interact closely with the host system and its resources (for more details, see cgroups).
This allows the CI Job to run another container on the host machine.
Here’s the docker-in-docker’s Dockerfile:
https://github.com/jpetazzo/dind/blob/master/fedora/Dockerfile
In 2013, Docker implemented the --privileged flag that makes DinD possible:
https://github.com/moby/moby/commit/280901e5fbd0c2dabd14d7a9b69a073f6e8f87e4
