
I started my career in December of 1989 at a company named Planning Research Corporation which contracted a considerable amount of work with the Department of Defense. I spent one year working in Fortran 77. The next 6 years were far more interesting to me as I dove into the world of ANSI C programming using the Kernighan & Ritchie bible. I still have my book on a shelf.
Our systems ran on 3 different Unix operating systems. We managed Makefiles that targeted SunOS, DEC Ultrix, and IBM AIX platforms. At times this was quite challenging. However, everything in this environment was 32 bit architecture; but what did that matter to me at the time? 64 bit processors didn’t come along for many more years.
Current Work Assignment
The current project I am working on has an application that scans a paper document, parses the image content, and computes some results. There is a graphical user interface that has a simplistic interaction with the user to obtain the document. That graphical user interface (GUI) is written for Java 1.6. The rest of the code is written in C++. Both front-end and back-end is targeted to run on a 32 bit hardware platform.
But now my customer wants to containerize it. So I looked around and found a 32 bit Ubuntu 16.04 image (https://hub.docker.com/r/i386/ubuntu/).
Multi-Stage Dockerfile
We needed to build the application on a
FROM i386/ubuntu:16.04 AS builder
RUN apt-get update -y && apt-get install -y g++ cmake ccache libssl-dev libboost-all-dev ... and others
WORKDIR /app
COPY src/ .
WORKDIR /app/build
RUN cmake ../src/RUN make
FROM i386/ubuntu:16.04
RUN apt-get update -y
RUN apt-get install -y libtiff5 libssl1.0.0 libssl-dev libboost-all-dev libudev-dev libx11-dev libxtst-dev ... and others ...
COPY --from=builder /app/build/lib*.so /usr/lib
COPY src/my.so.conf /etc/ld.so.conf.d/
RUN ldconfig
COPY --from=builder /app/build/bin/* /usr/bin
EXPOSE 6789/tcp
ENTRYPOINT /usr/bin/my-app
Notice there are two sections and each has its own FROM i386/ubuntu:16.04
line. The first stage is named AS builder
. The second stage is not named. However, two of its COPY --from=buider
commands reference the first stage by its name builder
.
That is the essence of the a multi-stage build. The first stage typically installs all relevant build artifacts and compiles the code. It typically contains an SDK which is not required for the runtime environment. The second stage contains the minimal runtime artifacts which in my example above are the shared objects and the executable.
First Road Block
To continue with my story, I now have an image that is ready to be deployed. I used
and waited for it to start up. It did not start. Docker Enterprise Swarm said that there were no resources that supported this container architecture. Well, that makes sense since all the nodes in the cluster are running Ubunutu 16.04 on 64 bit virtual machines.docker stack deploy -c my-stack.yml myapp
Decision Point
I came to a crucial decision point. I pondered the issue and generally came up with the following list of options. One of these approaches should help me move forward, but which one?
- Compile and run on
a 64-bit architecture. This would require some refactoring of the code base and lots of unknowns. - Work with the operations team to find a way to spin up some 32-bit VM’s and join them to the cluster.
- Continue down the path of 32-bit compilation but try running them in a 64-bit container.
I asked my colleagues about this and two of them sent me to different StackOverflow discussions that gave identical solutions which
Updated Dockerfile
FROM i386/ubuntu:16.04 AS builder
RUN apt-get update -y && apt-get install -y g++ cmake ccache libssl-dev libboost-all-dev ... and others ...
WORKDIR /app
COPY src/ .
WORKDIR /app/build
RUN cmake ../src/
RUN make
FROM ubuntu:16.04
RUN dpkg --add-architecture i386
RUN apt-get update -y
RUN apt-get install -y libc6-dbg libc6-dbg:i386 lib32stdc++6
RUN apt-get install -y libtiff5:i386 libssl1.0.0:i386 libssl-dev:i386\
libboost-all-dev:i386 libudev-dev:i386 ... and others ...
COPY --from=builder /app/build/lib*.so /usr/lib
COPY src/my.so.conf /etc/ld.so.conf.d/
RUN ldconfig
COPY --from=builder /app/build/bin/* /usrbin
EXPOSE 6789/tcp
ENTRYPOINT /usr/bin/my-app
In this newly refactored Dockerfile you can see that I have four changes.
- Changed the FROM line in the second stage to reference a 64-bit Ubuntu image.
Added dpkg --add-architecture i386
which ensures support for the i386 architecture.- Included three new libraries
libc6-dbg libc6-dbg:i386 lib32stdc++6
which provide runtime support for the 32-bit application. Append :i386
to my dependent libraries so that I get the proper implementation for my32 bit applications.
Success
This time when I deployed the container it spun up immediately and I was able to see my application running. This was a more than satisfactory solution for my client.
Summary
I learned a few things during this project. This is the first time I dove into implementing Docker multi-stage builds. I like embedding the build logic inside the container. It has the added benefits of having all the build tools encapsulated inside the Dockerfile and not necessarily installed on my laptop. It has the one drawback of having COPY
My learning extended into the arena of 32-bit vs64-bitt architectures. I learned that you cannot run 32-bit containers on a
These days I don’t normally work with C++ code. However, this has been a fun little project using some really cool Docker technology. I hope you enjoyed learning a bit more about it.
I work for Capstone IT and we would love to help your organization on your containerization journey.
Mark Miller
Solutions Architect
Docker Accredited Consultant