Docker in Docker actually makes a lot of sense, like the following scenario I encountered.

I need to use the CIDI service provided by the public cloud to trigger one-click build+test+deployment in the cloud, so I need an environment to build and release, but the build node in the cloud does not necessarily meet my criteria.

In the case of cloud build nodes we have no way to control (i.e. we can’t SSH directly to them).

It would be very comfortable to have a custom, stable Docker image that meets our requirements to serve as the environment for the entire CI/DI pipeline.

This is one of the goals of this article to achieve.

For example, I want the following environment.

  • Have a Maven + JDK17 environment to package my source code.
  • Have a Docker environment to push my packaged program as an image to a specified Docker product repository.

This environment is probably not available in the mainstream cloud CIDI anyway, mainly because JDK17 is too cutting edge. So let’s build a Docker image that has all of the above, well, that means we’re going to use Docker in Docker.

In fact, Alpine Linux’s Docker in Docker still has some pitfalls, the reason why I used this is also to make the image a little smaller. Fortunately, none of the problems are very big.

The commands and preparations provided below have been iterated several times and are complete. Other Alpine Linux Docker in Docker environments can also be changed based on this. So here is the official start of the display.


Your own environment should have Docker, because what we build is a Docker image specifically for building releases.

First confirm the Docker DNS, so that you don’t report an error when downloading software in Docker. alpine currently has this error.

Focus on the dns option below, make sure to set it.

  "registry-mirrors": [
  "exec-opts": [
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  "storage-driver": "overlay2",
  "dns": ["",""]

Build the environment image (Dockerfile) we need

FROM maven:3.8.5-eclipse-temurin-17-alpine
RUN sed -i 's/' /etc/apk/repositories
RUN apk update && apk upgrade && apk add curl wget bash  && apk add ca-certificates && update-ca-certificates \
    && apk add --update tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo "Asia/Shanghai" > /etc/timezone \
    && apk add docker\
    && rm -rf /var/cache/apk/*
CMD ["dockerd"]


  • apk update
  • apk add docker

These commands are all necessary

The others are for adding libraries, source settings, and timezone calibration, so you can adjust them yourself. For example, if you want a git environment, just add an apk add git to it.

The CMD startup command dockerd is used to start the Docker daemon. If you do not add it, you will get the following error when using the docker command.

Cannot connect to the Docker daemon at unix:/var/run/docker.sock. Is the docker daemon running?

Alpine Linux can’t use the systemctl command to start docker, it’s not that system.

The service library doesn’t exist either, so you can’t use our usual service docker start** command.

Execute the build command.

docker build -t docker-maven:jdk17 .

The docker-maven:jdk17 here can be replaced by yourself. Just as an example, the following is the same.


You need to give privileged permissions to run the container, otherwise it will report an error.

failed to start daemon: Error initializing network controller: error obtaining controller instance: failed to create NAT chain DOCKER: iptables failed: iptables -t nat -N DOCKER: iptables v1.8.7 (legacy): can’t initialize iptables table ’nat’: Permission denied (you must be root) Perhaps iptables or your kernel needs to be upgraded. (exit status 3)

The command to start is as follows.

docker run -itd --name dockermvn --privileged=true docker-maven:jdk17

Once you’re done booting, you can go in the container and have a blast!

docker exec -it dockermvn /bin/bash

Of course, I’m using this image as a build environment, so I don’t need to do anything in it, the actual build is automated in the cloud.

Then I just push it directly to the private repository and mount the environment directly in the pipeline console, adding a -privileged=true startup parameter and it’s OK.

The overall result is very satisfactory.