Extracting files from multi-stage Docker Builds using BuildKit

Extracting files from multi-stage Docker Builds - BuildKit

What seems to be the problem here officer?

reecetech use Docker multi-stage builds to run code compilation and test cases as a set of stages, copying the resulting application directory to a clean version of the runtime. This is a very neat model keeping a single Dockerfile to perform multiple tasks on the code. This also means tests have to pass before a runtime image is created.

However, we run into an issue when we want to take the resulting test coverage report and present it to Sonarqube. The test stage is lost in a multi-stage build as only the final stage is kept.

Gathering test coverage reports and other such artefacts from multi-stage Docker builds has been a pain for a while now. A few workarounds exist to gather these artefacts, such as running to the required --target stage and copying from the resulting Docker image.

The workarounds have always seemed heavyweight to me, there just has to be a better way.

There has to be a better way

Using Docker BuildKit (https://docs.docker.com/develop/develop-images/build_enhancements/) a very handy option has been provided to output the entire Docker image to disk as a directory or TAR ball.

This might sound like a super heavy way to get one file, but this one weird tip can help :) Using a scratch container and copying just the file you need from the test stage can give you what you have been screaming for.

Using the below stage in my Dockerfile, I can grab the PyTest coverage report into a scratch container (step 1):

##----------------------------------------------------------------------

FROM scratch AS export
COPY --from=pytest /tmp/coverage.xml .

##----------------------------------------------------------------------

Running the Docker build with DOCKER_BUILDKIT enabled, I can dump this stage to a directory (step 2):

#> export DOCKER_BUILDKIT=1
#> docker build --target export -t test . --output out

#> ls out/
coverage.xml

The above results in a new directory called out in the build directory with the only coverage report found inside, making it simple to pass this file onto Sonarqube.

This method still requires a second run of docker build to produce the final image, but with caching of the intermediate build images, the second build will be very fast.

In conclusion

Using the Docker built-in build enhancement BuildKit, extracting files from multi-stage builds as become easier. Utilising the power of minimalist scratch containers, we can output Docker images and contents to the local build directory.

Check out BuildKit today, you will not be disappointed!