Using Docker with Travis Continuous Integration

 Aug. 8, 2017  


GitHub supports several cloud-based continuous integration services. One of them is Travis CI that allows to run automated testing and deployment tasks on Linux Ubuntu and macOS. Unfortunately, their Ubuntu environment is quite dated — currently they use Ubuntu 14.04 LTS (Trusty Tahr) while the current LTS release is Ubuntu 16.04.2 LTS (Xenial Xerus). Users already asked Travis CI team about updating their Linux build environments to the current LTS release but Travis CI team's reply was basically "use Docker if you want anything else".

Unfortunately, Travis CI documentation about using Docker is not very clear. Maybe it makes sense for Ruby programmers but I'm more a Python guy and not familiar with Ruby at all. That is why I decided to provide a more generic example here. It uses Ubuntu 16.04 LTS Docker image to build and test a simple C++ program using cmake build system, but you can use any Docker image and programming language you like. This example assumes that you are familiar with Travis CI and its YAML-based configuration files, and also with Linux shell in general.

Here's my Travis CI config for testing a C++ build with Ubuntu 16.04 LTS Docker image:

 Click to show
sudo: required

services:
  - docker

# It is not really needed, other than for showing correct language tag in Travis CI build log.
language: cpp

env:
  matrix:
    - CC_VER: "5"
    - CC_VER: "6"

before_install:
  - docker run -d --name ubuntu-test -v $(pwd):/travis ubuntu:latest tail -f /dev/null
  - docker ps

install:
  - docker exec -t ubuntu-test bash -c "apt-get update;
    apt-get install -y software-properties-common python-software-properties;
    apt-get install -y build-essential cmake libboost-thread-dev libboost-date-time-dev;
    add-apt-repository -y ppa:ubuntu-toolchain-r/test;
    apt-get update;
    apt-get install -y gcc-$CC_VER g++-$CC_VER"

script:
  - docker exec -t ubuntu-test bash -c "cd /travis;
    cmake -DCMAKE_C_COMPILER=gcc-$CC_VER -DCMAKE_CXX_COMPILER=g++-$CC_VER . &&
    make &&
    chmod +x travis &&
    ./travis"

notifications:
    email: false

Let's focus of Docker-specific items of this config. Line 4 specifies that Docker is used in this build.

docker run command on line 15 pulls the necessary image (latest Ubuntu LTS in this case) from Docker repository and starts a docker container that is an isolated running "virtual machine" based on the Docker image.

-d option tells that our image will run in detached or background mode.

--name option sets the name of our container ("ubuntu-test")  so we can address it later.

-v option maps the current working directory ($(pwd)) into our Docker container under /travis directory so we can access our build files from within the container.

ubuntu:latest is the name of our Docker image that our build container is based on.

The problem with docker run command is that our container immediately stops if we do not provide it with any commands, so we need to keep it running somehow. And this is what the command tail -f /dev/null used for — it blocks the shell inside the container and prevents it from stopping, so we can send our commands to it.

docker ps command on line 16 prints the list of running containers. It is optional and used only for diagnostic purposes.

docker exec command on line 19 allows to execute a command or a series of Linux shell commands inside our container.

-t options allocates a pseudo-TTY for this command(-s) so you can view output or error messages, if any, in Travis CI build log.

ubuntu-test is the name of a container we send commands to.

bash -c means that we will execute the following string in bash shell.

YAML allows to span strings enclosed in quotes (") into several lines and I recommend to put each command on a separate line for better readability. Bash commands are separated with a semicolon (;).

In install section we are installing our build prerequisites, using build matrix variables, if necessary. In build section we are using docker exec again to run build commands. Note that here commands are separated with double ampersands (&&) instead of semicolons. With double ampersands each subsequent command will run only if the previous commands succeeds. If a commands fails, the subsequent commands won't be executed and  docker exec command will return a non-zero error code resulting in Travis CI build failure.

I don't provide source for my test C++ program and its CMakeLists.txt here because they don't really mater. Using docker exec you can run virtually any commands in your Docker container.

  Continuous IntegrationGitHub