32-bit Apps in a 64-bit Docker Container

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 32 bit target architecture. I thought that since I don’t have access to one, I might as well use a 32 bit container. I also wanted a slimmed down runtime environment. So, I decided on using Docker’s multi-stage build process. The first stage copies in the source code and compiles it. The second stage copies the binaries from the first stage into the second stage and configures the runtime environment. Here is an outline of what my first multi-stage build looked like.

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 docker stack deploy -c my-stack.yml myapp 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.

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?

  1. Compile and run on a 64-bit architecture. This would require some refactoring of the code base and lots of unknowns.
  2. Work with the operations team to find a way to spin up some 32-bit VM’s and join them to the cluster.
  3. 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 was the third approach above. I need only to install 3 libraries in order for the 32 bit application to run inside a 64 bit container. In addition, my applications dependent libraries needed to be prefixed with i386 to get the right version for runtime.

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.

  1. Changed the FROM line in the second stage to reference a 64-bit Ubuntu image.
  2. Added dpkg --add-architecture i386 which ensures support for the i386 architecture.
  3. Included three new libraries libc6-dbg libc6-dbg:i386 lib32stdc++6 which provide runtime support for the 32-bit application.
  4. Append :i386 to my dependent libraries so that I get the proper implementation for my 32 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 to COPY all the source code into the container which can take a bit of time on a large project.

My learning extended into the arena of 32-bit vs64-bitt architectures. I learned that you cannot run 32-bit containers on a 64 bit virtual machine. I learned that you can install 32 bit applications inside of 64 bit container with a little Dockerfile configuration.

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


Leave a Reply