Docker inside docker inside docker - how deep must we go?
Introduction
You must have heard of the term DIND(Docker inside Docker). Although, it should be avoided if one can get by without using DIND. It essentially means running docker inside a docker image.
But imagine an use case where you build docker images in your CI/CD machines after ’testing’ the code. Now ideally, we want these tests to run in a containerized environment. We build and push an image from associated Dockerfile ‘iff’ the tests pass.
So here are two ways we can do this.
- We store the results of the tests in storage which the host system can access / STDOUT. And if the tests run successfully, we work with docker images in the host system.
- Method 1 can be hard to achieve when you have multiple interactions between the two workflows, i.e code related utility(tests) and software delivery. For instance, if one has to build different images, or a different number of images which vary conditionally with the test results. In such cases, we can run “docker commands” tightly coupled with the code utilities. Something along the lines of:
Or something in a Makefile like:
if not <sanity-check-statement>: os.system("docker build <stuff>")
if <python run_test.py returns 0>: make docker-build
The second method clearly doesn’t need DIND, instead it needs just the docker-client and we can use the host’s daemon to run the commands. To achieve this, we can use an image with docker-client installed and volume map the .sock
file as mentioned here. My senior at work @satyamkrishna92 introduced me to this semi-DIND concept. I knew at that instant what I was going to do.
And no, it wasn’t working on the problem at hand. [drumrolls] I wanted to spawn some containers recursively from within these containers, albeit in a controlled manner :P.
DINDINDIND…
Welcome to DINDINDIND.., Docker inside Docker inside Docker inside Docker..(and so on) !
The goal is to start N-i-1
docker containers recursively within the ith
container.
A recursive psuedo-code function can be as follows:
def spawn_container(n):
print("running in ith container")
if n > 0:
spawn_container(n-1)
# else exit gracefully.
The plan of action is as follows.
- Have an environment variable called
RECURSIVE_DOCKER_COUNTER
which stores how many containers should we spawn recursively. This is the variablen
. - Check if
n > 0
, spawn next container by volume mapping the daemon socket file and settingRECURSIVE_DOCKER_COUNTER = RECURSIVE_DOCKER_COUNTER - 1
for the “child” container.
This repository has the code required for reference. I’ve used a Python script to implement the tiny logic mentioned above, curse my poor Shell Scripting skills.
Run the following to run the experiment,please make sure you have make installed, otherwise you can run the commands in the Makefile directly as well.
$ git clone https://github.com/plant99/dindindind
$ make experiment
Please allow me to explain some of the bits if it is needed.
-
Looking at the
Dockerfile
FROM docker:latest FROM python:3.7 RUN apt-get update && apt-get install -y gcc libc-dev COPY --from=0 /usr/local/bin/docker /usr/bin/docker COPY . /recursive-docker WORKDIR /recursive-docker CMD ["python", "spawn_container_util.py"]
This image is built on top of docker-client image and Python 3.7 in steps. It simply runs
spawn_container_util.py
which has some more logic we’ll look at next. -
Looking at
spawn_container_util.py
import os if __name__ == "__main__": counter = int(os.environ.get("RECURSIVE_DOCKER_COUNTER", 0)) if counter > 0: counter_offset = 10 - counter print(f"running from counter{counter_offset}") counter -= 1 os.system(f"docker run --env RECURSIVE_DOCKER_COUNTER={counter} -v /var/run/docker.sock:/var/run/docker.sock recursive-docker:latest")
This script obtains the counter, checks if it is greater than 0, decreases it by 1. Finally, spawns another image with daemon socket volume mapped with the host.
-
We trigger the first container with
RECURSIVE_DOCKER_COUNTER=10
to spawn 10 images.$ docker run --env RECURSIVE_DOCKER_COUNTER=10 -v /var/run/docker.sock:/var/run/docker.sock recursive-docker:latest
NB: While working on this, I ran into a situation where the daemon started spawning “infinite” number of containers due to a bad check I was doing, and I rebooted the system. But from the logs it looked like the daemon stopped spawning containers after a point. I’m yet to figure out what went behind that, if you know about it, please write to me.
That’s all folks, hope you have a great day! Thanks for reading!