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 usingoverlay2
. - 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.
This script does the following
- Creates a temporary ext4 volume and mounts it on
/var/lib/docker
- Starts the Docker daemon in the background
- Pulls the image(s)
- Stops the daemon
- Moves the files from
/var/lib/docker
to/opt/temp
- Unmounts and removes the temporary volume and file
- 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.