Run nested docker daemon (Docker-in-Docker)

in #docker15 days ago

Create a container image for Docker-in-Docker

"Docker-in-docker" is an often useful technique to help running multiple instances of container clusters (e.g. swarm services, docker-compose) on the same container host. It consists of:

  • A conventional container image with docker server daemon and client utilities.
  • A privileged container running the image, which has its entrypoint script to start the nested docker server daemon.
  • Other techniques applied optionally to enhance the server daemon performance.

Let's build the container image. First, prepare a utility script called modprobe:

#!/bin/sh
set -eu

# "modprobe" without modprobe: https://twitter.com/lucabruno/status/902934379835662336
for module; do
        if [ "${module#-}" = "$module" ]; then
                ip link show "$module" >/dev/null 2>&1 || true
        fi
done

export PATH='/usr/sbin:/usr/bin:/sbin:/bin'
modprobe "[email protected]" >/dev/null 2>&1 || true

Next, create the entrypoint script that launches docker daemon:

#!/usr/bin/env bash
set -euo pipefail

# Prepare the container for docker-in-docker operation, the routine is taken from https://github.com/moby/moby/blob/master/hack/dind.
export container=docker
if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security; then
        mount -t securityfs none /sys/kernel/security || {
                echo >&2 'Could not mount /sys/kernel/security.'
                echo >&2 'AppArmor detection and --privileged mode might break.'
        }
fi
if ! mountpoint -q /tmp; then
        mount -t tmpfs none /tmp
fi

# Optimise docker daemon performance by presenting it with a cache directory, backed by a conventional file system, so that the docker daemon can put file system layers in it.
mkdir -p "/mnt/storage"
find "/mnt/storage/" -maxdepth 1 -mindepth 1 -type d -not -name "builder" -not -name "image" -not -name "overlay2" -exec rm -rf {} \;
storage_args=''
if ! mountpoint -q /mnt/storage; then
  storage_args='--storage-driver=vfs'
fi

# Use setsid to detatch the docker daemon from the terminal, otherwise it often catches a wild signal and terminates prematurely.
setsid dockerd -p "/var/run/docker.pid" -l debug --data-root "/mnt/storage" $storage_args &>>/tmp/dockerd.log &

# Wait for docker daemon and its firewall to get ready
while [ ! -f '/var/run/docker.pid' ] || [ ! -e '/var/run/docker.sock' ] || ! (iptables -L FORWARD | grep -q DOCKER-USER); do
    sleep 0.1
done

# Execute the container launch parameters
exec "[email protected]"

And finally the Dockerfile builds the image:

FROM ubuntu:18.04

ENV DEBIAN_FRONTEND noninteractive

# Install docker and its dependencies. "kmod" is used by /usr/local/bin/modprobe via "lsmod" command, which is itself a dependency of dind.
RUN apt-get update && apt-get install -q -y -f -m -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef busybox ca-certificates curl docker.io iproute2 kmod
COPY modprobe /usr/local/bin/modprobe
RUN chmod 755 /usr/local/bin/modprobe

# Optional: install docker-compose for development and testing activities
RUN curl -L "https://github.com/docker/compose/releases/download/1.25.1/docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose && chmod 755 /usr/local/bin/docker-compose

# The entrypoint will start the nested docker server daemon
COPY entrypoint.sh /entrypoint.sh
RUN chmod 755 /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

Give it a go:

sudo docker build -t my-dind .
sudo docker run -it --rm --privileged my-dind bash
# In docker-in-docker container bash shell:
docker run --rm hello-world

If you are using this docker-in-docker image on a Linux host, you can help speeding up its operations by offering it a cache directory to work with:

mkdir docker-cache
sudo docker run -it --rm --privileged -v "$(readlink -f docker-cache)":/mnt/storage my-dind bash

And enjoy!