Setting up CI for a C++ application with Docker

Having an automated way to build an application and run tests is a convenient way to learn about failures as quickly as possible. It’s as important for businesses as it is for smaller open-source projects.

Why use Docker on CI?

There are a few reasons why it’s worth to use Docker in a continuous integration environment. Keeping the build process isolated from the rest of the system is an important one. Having control of the operating system and other dependencies is important when system libraries or programs are needed. I’ve found it particularly useful in the context of C++ where, due to lack of widely adopted package managers, installing libraries matching required versions is cumbersome. Popular continuous integration platforms available to open source software usually offer solutions involving old GNU/Linux distributions and therefore make it difficult to install newer versions. In the C++ world where building applications from source is time consuming and often non-trivial, it’s a major pain. But that’s where Docker can help.

C++ application built with Docker on CI

I’m working on a C++ desktop client for Postgres. I want to build and run the tests automatically after pushing my changes. Some of the dependencies, however, are heavy (e.g. GTK+ and its C++ bindings) and TravisCI offers Ubuntu 14.04 where only older versions are available. Fortunately, it can be built in a Docker container where operating system can be picked and libraries can be installed.

.travis.yml config file becomes very simple in this case as it only builds the image:

1language: cpp
2sudo: false
3
4jobs:
5  include:
6    - stage: ubuntu1804
7      script:
8        - cd ci
9        - docker build -t sancho_ubuntu1804 -f Dockerfile.ubuntu1804 ./

Dockerfile is located in the ci/ directory - it’s currently designed to work with recent stable version of Ubuntu - 18.04.

1FROM ubuntu:18.04

Libraries are easily installed with Ubuntu’s package manager:

1RUN apt-get update && apt-get install -y \
2    pkg-config \
3    git \
4    g++ \
5    cmake \
6    libxml2-utils \
7    libgtkmm-3.0-dev \
8    libgtksourceviewmm-3.0-dev \
9    libpqxx-dev

Minor complication is that we need to get the GoogleTest library by installing it from source:

1RUN git clone https://github.com/google/googletest.git /googletest \
2    && mkdir -p /googletest/build \
3    && cd /googletest/build \
4    && cmake .. && make && make install \
5    && cd / && rm -rf /googletest

Afterwards, we just need to clone the project:

1RUN git clone https://github.com/lchsk/sanchosql.git /sanchosql

Then, we’re ready to build the application and tests:

 1RUN echo "Building the application" \
 2    && cd /sanchosql \
 3    && mkdir build \
 4    && cd build \
 5    && cmake .. \
 6    && make -j
 7
 8RUN echo "Building tests" \
 9    && cd /sanchosql/tests \
10    && mkdir build \
11    && cd build \
12    && cmake .. \
13    && make -j

If they are built successfully, the last step is simply running the tests:

1RUN echo "Running tests" \
2    && cd /sanchosql/tests/build \
3    && ctest

This solution can also be easily tested locally.

1cd ci
2docker build -t sancho_ubuntu1804 -f Dockerfile.ubuntu1804 ./

This will build the application and run tests.

We can then run

1docker run --rm -P -it --name sancho_build sancho_ubuntu180

to open shell and manually check if things look right inside the container by navigating to the application’s directory:

1cd /sanchosql

This solution is easily adaptable to various use cases and can be extended to more complex applications.