Compile code with Docker

Personally, I find Docker extremely helpful for creating a build environment. I don't need to install tools and libraries on my local computer or on a virtual machine. I can build an environment for each library or application I want to build, I can throw it away and then recreate it every time I need.

I can have multiple versions of the same tools, but in different containers, so I don't need to worry about conflicts. I can commit my Dockerfile into some git repository, I can change computer, checkout the Dockerfile and I am ready to build. No more "it works for me" excuses :)

Pull base image

There are many images which we can use to create our build environment. We can choose among Ubuntu, Debian, Fedora, Centos, and others, which are available on Docker Hub.

Let's assume we choose Ubuntu. We can pull any version we want just passing a tag:

docker pull ubuntu:latest
docker pull ubuntu:16.04

Create Dockerfile

Let's assume we want to create our build environment using Ubuntu as operating system.

All we have to do is creating a Dockerfile, where we declare the commands required to install the tools we need, for example the GNU C compiler:

mkdir build-compiler-image

cat <<EOF >build-compiler-image/Dockerfile
FROM ubuntu:16.04
RUN apt-get update -y && apt-get install -y gcc

Build custom image

We can create a Docker image using our Dockerfile:

docker build -t build-c build-compiler-image

Compile and execute code

We can easily compile and execute a simple C program using our custom image:

mkdir -p project/src project/bin

cat <<EOF >project/src/main.c
#include <stdio.h>
int main(int argv, char ** argc) {
    printf("Hello Docker!\n");

docker run -it -v $(pwd)/project:/project build-c gcc -o /project/bin/main /project/src/main.c

docker run -it -v $(pwd)/project:/project build-c /project/bin/main

Cross-compile code

We can install cross compilation tools like Mingw-w64, and compile code for Windows:

mkdir build-mingw-w64-image

cat <<EOF >build-mingw-w64-image/Dockerfile
FROM ubuntu:16.04
RUN apt-get update -y && apt-get -y install mingw-w64

docker build -t build-mingw-w64 build-mingw-w64-image

mkdir -p project/src project/bin

cat <<EOF >project/src/main.c
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    MessageBox(NULL, "Hello World!", "My app", MB_OK);

docker run -it -v $(pwd)/project:/project build-mingw-w64 /usr/bin/i686-w64-mingw32-gcc -o /project/bin/main.exe -luser32 /project/src/main.c

Customise a library

We can compile a customised version of a library like ffmpeg:

mkdir build-autotools-image

cat <<EOF >build-autotools-image/Dockerfile
FROM ubuntu:16.04
RUN apt-get update -y && apt-get -y install git make gcc autoconf nasm yasm pkg-config

docker build -t build-autotools build-autotools-image

mkdir output

docker run -it --rm -v $(pwd)/output:/output build-autotools git clone /output/ffmpeg
docker run -it --rm -v $(pwd)/output:/output build-autotools bash -c "cd /output/ffmpeg && ./configure --enable-gpl --disable-ffserver && make"

Execute isolated builds

One common issue with CI servers is managing multiple versions of the tools required for building code. Managing multiple versions of different tools means that we need to install and configure several plugins in our CI server and we need to configure our builds to use the correct version of those tools.

A simpler approach, which is available in Jenkins and other CI/CD solutions such as CircleCI, would be to use Docker to create the environment with the required tools for executing our builds. Using this approach we relay on Docker for creating isolated build processes which can easily run in parallel.