It was like any other day working on micro-services project, running on Docker environment. In general, we’ve had worked on making our Image Builds more efficient, secure, and faster following basic aspects that significantly affect building and working with Docker.
- Understanding Docker layers and structuring the Dockerfile to maximize their efficiency.
- Reducing the weight of the Docker image, by being specific about our Base Image Tags which comes up with minimal packages.
- Bringing the multi-stage builds concept, etc.
But keeping the spirits of being highly productive and improving more, I landed upon
Docker BuildKit. Docker BuildKit is the next generation container image builder, which helps us to make Docker images more efficient, secure, and faster. It has been lingering in the background of Docker builds for some time. Moreover to enable and unleash some massive performance is to set the
DOCKER_BUILDKIT=1 environment variable when invoking the docker build command, such as:
$ DOCKER_BUILDKIT=1 docker build .
In this post, we’ll walk through with some of its powerful features which I have explored and came up to these results as below :
Considering a Multi-stage Build, when BuildKit comes across a multi-stage build it get concurrency, it analyzes the docker file and create a graph of the dependencies between build steps and uses this to determine which elements of the build can be ignored; which can be executed in parallel; and which need to be executed sequentially. For example, the stages as
build 1 and build 2 can be run in parallel as they don’t depend on each other however the final build stage depends on the other two so it will be build afterwards when both the other two stages are completed.
FROM alpine AS build1 RUN touch /tmp/dee.txt RUN sleep 10 FROM alpine AS build2 RUN touch /tmp/sandy.txt RUN sleep 10 FROM alpine AS final COPY --from=build1 /tmp/dee.txt /tmp/ COPY --from=build2 /tmp/sandy.txt /tmp/
And for more checks I’ve added a delay of
10s to the
build 1 and build 2 stages. When using the legacy build engine it executes top to bottom so it will execute two separate sleep commands of 10s which accumulates to a wait time of
20s, nevertheless using BuildKit it will execute both the sleep commands at the same time and therefore only accumulate a sleep time of
Hence, the standard Docker comes without concurrency in which build command performs builds on Dockerfile sequentially, which means it reads and builds each line or layer of the Dockerfile at a time and as a result it took
49.135s. Whereas BuildKit, allows for parallel build processing resulting in better performance and faster build times thus it only took
27.2s to build it.
Build Secrets Volumes
Sometimes we need some secret keys or password to run our build, just like credentials to access AWS S3 repository. In classic Docker builds there is no good way to do this; the obvious methods are insecure, and the workarounds are hacky. Therefore, BuildKit adds support for securely passing build secrets, which allows build container to access secure files such as secret access keys without baking them into the image.
# syntax = docker/dockerfile:1.2 FROM python:3 RUN pip install awscli RUN --mount=type=secret,id=aws,target=/root/.aws/credentials aws s3 cp s3://walletprodtest/terraform ./ --recursive
To build the image,
$ DOCKER_BUILDKIT=1 docker build --secret id=aws,src=/root/.aws/credentials .
The way BuildKit secrets work is that a file with the secret gets mounted to a temporary location during the
RUN command, e.g.
/root/.aws/credentials. Since, it’s only mounted during a particular
RUN command, it doesn’t end up embedded in the final image.
BuildKit mount types doesn’t end only with
secret, we have few more :
- Cache Mount : Sick of re-downloading all external dependencies every time when there’s a change to only one of them, the cache mount can help us save time in the future. Inside of our Dockerfile, add a mount flag, specifying which directories should be cached during the step.
RUN --mount=type=cache,target=/var/lib/apt/lists …
- SSH Mount : This mount type allows the build container to access SSH keys via SSH agents, with support for passphrases.
RUN --mount=type=ssh … etc.
Using these mount types features, makes it easier for us to pass build-time secrets, handle SSH credentials, and cache directories in between builds even if the layer needs to be rebuilt.
This feature is an upgraded version from the
--cache-from which was having problems such as images need to be pre-pulled, no support for multi-stage builds, etc. With BuildKit, you don’t need to pull the remote images before building since it caches each build layer in your image registry. Then, when you build the image, each layer is downloaded as needed during the build.
For example, keeping the Dockerfile same as used in Build Secrets Mount, we build the image using,
$ DOCKER_BUILDKIT=1 docker build -t dgupta9068/demo --secret id=aws,src=/root/.aws/credentials --build-arg BUILDKIT_INLINE_CACHE=1 . $ docker push dgupta9068/demo
Now will make a small change in the Dockerfile by adding one more
RUN command which will create a
test directory. After this, prune the Docker system and try to rebuild the image using
$ DOCKER_BUILDKIT=1 docker build --cache-from dgupta9068/demo .
To use an image as a cache source, cache metadata needs to be written into the image on creation which was done by setting
--build-arg BUILDKIT_INLINE_CACHE=1 when building the image.
After that, for the second fresh image build, we used
dgupta9068/demo image as cache source by which BuildKit automatically pulled up all the cached layers and just executed the last
RUN layer of creating a directory. And hence, ended in a faster build of the Docker Image.
I hope this article will give a kickstart to use and knowing more about BuildKit and its features which will help you to simplify and speed up your Docker build workflows. If you want to learn more about fasten-docker-build , besides BuildKit – do checkout !
Till then keep building 🙂
Reference – GIF
Blog Pundit: Abhishek Dubey
Opstree is an End to End DevOps solution provider