Pre-pull images in the docker-in-docker image

We needed to pre-pull images into the upstream docker:dind image. Here's a way of doing this.

We needed to pre-pull images into the upstream docker:dind image. Here's a way of doing this.

Overview

Our builds always use the same upstream images, we're tired of re-pulling them every time it builds. It adds a lot of unnecessary time when they can be pre-cached in our build image.

As with most things with shell scripts and Docker, you will want to do this in a Linux or WSL environment.

Solution

We already use the docker:dind image in our on-prem build environment so it made sense to continue using that image and extend it. Everywhere we looked, people say this can't be done, I didn't believe them, so I did it.

I broke down the steps, start the docker daemon, pull the images, call it a day. It wasn't as simple as that for some reasons explained below.

Problems

  • The Docker daemon requires --privileged on the run command. Meaning it has full access to the system. This is not supported (out of the box) by Docker.
  • When I finally got it running it was using the vfs volume driver, when running as a regular container it was using overlay2.
  • The overlay2 storage driver is not compatible with storage provided by the build context.

How I did it

Create the buildx build context

First step, start the docker daemon during the build. This required running the build with elevated privileges. To do this we had to create a special buildx build context with a couple of flags enabled. To create that I used the following command:

docker buildx create --name builder \
    --driver docker-container \
    --buildkitd-flags '--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host'

This command creates a new buildx build context named builder using the docker-container driver with the ability to run a privileged RUN command in the Dockerfile.

Create the Dockerfile

The Dockerfile is a simple one, we set the syntax to a special one that allows us to use --security=insecure in the RUN command.

# syntax=docker/dockerfile:1-labs

FROM docker:dind

COPY install.sh /opt/install.sh
RUN --security=insecure /opt/install.sh

This will run the /opt/install.sh command with elevated privileges.

Create the install.sh script

Here's the link to install.sh in my git repository. It's large enough to not want to paste it into my page.

prepull-dind/install.sh at main ยท EdwardCooke/prepull-dind
Contribute to EdwardCooke/prepull-dind development by creating an account on GitHub.

This script does the following

  1. Creates a temporary ext4 volume and mounts it on /var/lib/docker
  2. Starts the Docker daemon in the background
  3. Pulls the image(s)
  4. Stops the daemon
  5. Moves the files from /var/lib/docker to /opt/temp
  6. Unmounts and removes the temporary volume and file
  7. Moves the files back

To increase the size of the temporary volume, in case you're copying in a bunch of base images, modify the dd line in install.sh to be an appropriate size. The size of this file doesn't affect your overall image since it's removed at the end.

To pull other images you will modify this file, and change the line that currently pulls alpine:latest to pull whatever images you would like.

Don't forget to mark this file as executable.

chmod +x install.sh

Build the image

Now we have all of the files in place. To build the image we will use buildx, our build context builder, allowing security.insecure and pushing into our main Docker context.

docker buildx build \
    --allow security.insecure \
    --output type=docker \
    -t mydind \
    --builder builder \
    --progress=plain \
    .

Test

To test this, we will start the docker image we just built and in another terminal, run docker image ls in that container and see the alpine:latest image is there.

Start it

docker run -it --rm --privileged --name dind mydind

Validate

docker exec -i dind docker image ls

You should see this output

REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
alpine       latest    c1aabb73d233   6 weeks ago   7.33MB

Caveat

If you want to create a volume for your /var/lib/docker directory you'll need to do a little more work. In the install.sh you won't want to move the files back to /var/lib/docker. Instead, you will need a new entrypoint script that checks to see if /var/lib/docker has anything in it and if not, recursively copy the /opt/temp over to that directory. After that, have the script execute exec dockerd-entrypoint.sh $@.

Conclusion

I had a lot of fun with this. I like little projects where everyone says you can't do something. It makes it a fun challenge.

GitHub - EdwardCooke/prepull-dind
Contribute to EdwardCooke/prepull-dind development by creating an account on GitHub.
Dockerfile reference
Find all the available commands you can use in a Dockerfile and learn how to use them, including COPY, ARG, ENTRYPOINT, and more.
Docker