Docker multi-stage example

 

A use-case of the benefits of Docker multi-stage builds

Multi-stage is a way of building a Docker image in stages so that the actions of one stage do not affect other stages.

Some time ago I reported a bug in Gambit Scheme compiler. The bug was fixed quickly but the actual fix was released quite a bit later in the next release version. I wanted to test and verify the fix in an actual release version so I could close the issue I had reported. Unfortunately there wasn’t a ready-made release binaries available at that time, so I decided to build the project myself. There were couple of challenges. First of all, I didn’t know if the bug was completely fixed or if I have to actually test multiple versions. I didn’t want to pollute my computer with compilers, tools and libraries and possibly multiple versions of compiler Gambit binaries. Secondly, I didn’t want to pollute the actual Gambit installation with any compiler tools or libraries. I wanted to check the bug fix with a clean installation like it was normally installed to an end-users machine.

Docker manages the first challenge easily. Simply create an image that has all necessary libraries and compilers and of course the source code of the project itself. The second challenge is a bit trickier. Should the compiler, tools and libraries be cleaned up from the image before the testing or should there be a second image where the compiled project would be copied? Docker provides multi-stage builds precisely for this kind of laborious task.

The complete Dockerfile looks like this:

FROM ubuntu:22.10

ENV TZ=Europe/Helsinki

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

RUN apt update && \
apt install -y build-essential git

WORKDIR /build
RUN git clone --depth 1 --branch v4.9.4 https://github.com/gambit/gambit.git

WORKDIR gambit
RUN ./configure --enable-single-host --enable-march=native --enable-dynamic-clib --prefix=/usr/local
RUN make -j8
RUN make check
RUN make install

FROM ubuntu:22.10
COPY --from=0 /usr/local /usr/local
CMD ["gsi", "-:s"]
  1. From Dockerhub, use Ubuntu version 22.10 as a basis
  2. Make couple of magic tricks to get timezones working so the apt command won’t complain about them
  3. Install compilation tools and git by using apt command
  4. Change the working directory to the /build folder. We will clone the project source code under this folder.
  5. Clone the desired version of the Gambit source code. In this case the version tag is “v4.9.4”. Also, because we don’t need a version history, we can leave it out by using the attribute “–depth 1”.
  6. Git will clone the source code under the folder named after the project name, so we can change the working directory to the “gambit” folder
  7. Execute necessary commands to compile and install Gambit. Specify the installation folder as /usr/local.

So far this has been quite basic Docker stuff. Building an image using the listed steps, we would get an image that contains all required compiler tools and libraries, source code and compiled and installed Gambit binaries inside /usr/local folder.

Next, we will change this Dockerfile to a multi-stage file by adding a new FROM line at the end of the file. This is the signal for Docker to perform the build process as a multi-stage build.

  1. Like before, we will use Ubuntu 22.10 version as a basis for our image
  2. We will copy from the previous stage (stage 0) contents of the /usr/local folder to the /usr/local folder of this new image we are creating. Docker knows to perform this copy operation when it sees the following attribute: “–from=0”.
  3. Finally, we will define a command that will be executed when this image is started as a new container instance. Command line “gsi -:s” is a Gambit-specific command that will start an interactive shell and load a set of libraries that are critical for our testing.

The Dockerfile we have created will be built like any normal Dockerfile by using the command:

docker build -t gambit:4.9.4 .

After the build process we should have a relatively small Docker image:

REPOSITORY  TAG    IMAGE ID      CREATED         SIZE
gambit      4.9.4  1e421b726dbd  24 minutes ago  220MB

This image does not contain any compilers, tools, libraries or source code from the first stage. The image has only the basic Ubuntu installation and the Gambit installation under /usr/local folder.

Now we can start the testing by giving the command:

docker run -it --rm gambit:4.9.4

You can read more information about Docker multi-stage builds from here: Multi-stage builds (at docker.com)

Writer is Matti Kärki, a senior developer from Ouro

Previous Post
It has always been computers – Marta’s career story
Next Post
Docker tags explained