Docker-in-Docker Without Privileged Containers

Usually, Docker containers are deployed on a regular, non-containerized host system. Still, there are valid use cases that require a configuration known as Docker-in-Docker (DinD), where a Docker container is running on the host system and within that container runs yet another Docker container. The usual approach is to run the host container with the --privileged option and use a special docker:dind image.

A great disadvantage of privileged containers is that sandboxing is virtually eliminated — the container can do almost everything the host can, even obtain a root shell in the host system. Unfortunately, with a standard Docker setup, DinD cannot be achived in an unprivileged container.

Sysbox Container Runtime

To run DinD in an unprivileged container, you need to have control over the host system. Then, you can install an alternative container runtime that supports running the Docker daemon inside an unprivileged container: Sysbox.

Behind the scenes, going from the highest level to the lowest, Docker uses containerd to manage containers, and containerd uses the runc container runtime to spawn the containers. Sysbox is one of the alternatives to runc that implement the shim API, and can therefore be used with containerd, consequently with Docker. When you install Sysbox, it won’t interfere with runc, which will remain the default container runtime.

To install Sysbox, you can either use a prebuilt package for supported Linux distributions, or compile it from source. Refer to the official installation instructions.

Using Sysbox DinD Containers

Before starting any container that uses the Sysbox runtime, the sysbox service must be running:

[host] # systemctl start sysbox

Then, when starting the container, set the --runtime option to sysbox-runc. Do not use the docker:dind image; Sysbox doesn’t require special workarounds for running DinD. For example:

[host] # docker run --runtime=sysbox-runc -it --rm archlinux:latest bash

In the container, install Docker as you would on a non-containerized host. Prior to running any docker commands, the Docker daemon must be running. This is usually done with a systemd service, however, container images typically don’t include systemd. You can spawn dockerd in the background manually. For example:

[container] # pacman -Sy docker
[container] # dockerd >/var/log/dockerd.log 2>&1 &
[container] # docker run -it alpine:latest sh
BONUS TIP

If you’re building your own container image, you can create a wrapper around the docker executable that spawns dockerd when needed. For example:

FROM archlinux:latest

RUN pacman -Sy --noconfirm docker docker-compose; \
  echo '#!/bin/sh' > /usr/local/sbin/docker; \
  echo 'if ! (pgrep dockerd >/dev/null 2>&1); then' >> /usr/local/sbin/docker; \
  echo '    dockerd >/var/log/dockerd.log 2>&1 &' >> /usr/local/sbin/docker; \
  echo 'fi' >> /usr/local/sbin/docker; \
  echo 'exec /usr/sbin/docker "$@"' >> /usr/local/sbin/docker; \
  chmod 755 /usr/local/sbin/docker

ENTRYPOINT ["bash"]