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 "$@" >/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 "$@"
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!