We needed to pre-pull images into the upstream
docker:dind image. Here's a way of doing this.
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.
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.
- The Docker daemon requires
--privilegedon 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
vfsvolume driver, when running as a regular container it was using
overlay2storage driver is not compatible with storage provided by the build context.
How I did it
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 is a simple one, we set the syntax to a special one that allows us to use
--security=insecure in the
# 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.
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
- Starts the Docker daemon in the background
- Pulls the image(s)
- Stops the daemon
- Moves the files from
- 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
security.insecure and pushing into our main Docker context.
docker buildx build \ --allow security.insecure \ --output type=docker \ -t mydind \ --builder builder \ --progress=plain \ .
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.
docker run -it --rm --privileged --name dind mydind
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
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 $@.
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.