With Docker, we can package our application in a Docker image and we have the assurance that it will run on any machine that has Docker. We can do the same with our build plan. Dockerizing the build environment means that the only dependency we have on the build server is that it supports Docker. This reduces the amount of work needed to manage the build server and enables teams to be more independent.

Update 2018-08-26 See also Dockerize the build plan v2.0

To do that, we’ll need to write a separate Dockerfile. Let’s call it Dockerfile-ci. It looks similar to the main Dockerfile at first:

FROM node:8.9-alpine
RUN mkdir -p /app
ADD package.json /app
ADD package-lock.json /app
RUN npm install
ADD . /app

It has some differences with the main Dockerfile:

  • it doesn't have an EXPOSE instruction, because we're not going to run the application.
  • it doesn't have a CMD instruction, because we'd like to run any command
  • it installs all dependencies, including dev dependencies, which will produce a larger image

To build the image we run:

docker build -t blog-helm-ci -f Dockerfile-ci .

This new image named blog-helm-ci contains the application code and all of its dependencies and it is ready to run any command we want.

We can use the image to run linting:

docker run blog-helm-ci npm run lint

This is the same as running npm run lint, but we don’t need to install node on the build server, or having to worry about managing node versions, etc.

Since we’re talking about build servers, when running this on a build server, we’d like to be able to see the linting results in a human friendly way, instead of trying to figure out what went wrong by diving into the build’s log file. We can configure ESLint to generate an XML report with the problems it finds, using the ubiquitous jUnit XML report format.

To do that, we’ll first configure an additional npm script to run ESLint with XML output. In package.json, we need to add the following in the scripts section:

    "lint-junit": "eslint -f junit -o test-reports/eslint.xml .",

This gives us a new npm script, npm run lint-junit, which will generate an XML report at test-reports/eslint.xml if there are any linting issues. If we configure the build server to read this file, we will get a friendly report with the problems we need to solve. Note that we can do the same with unit tests, we just use linting as a simpler example.

We’ll need to rebuild the our docker image and then run the new script:

docker run blog-helm-ci npm run lint-junit

And… nothing happened! There is no test-reports folder at all. What went wrong?

Remember that the commands are running within a Docker container. They are running against the filesystem of that container. So the test-reports got created fine, but it was created inside the container, not on the physical host (e.g. personal laptop or build server).

Containers are ephemeral; to preserve data, we need to use a Docker volume. We’ll need to define in Dockerfile-ci that the test-reports folder is a volume:

VOLUME [ "/app/test-reports" ]

This allows us to mount this folder. The run command looks like this:

docker run \
  -v $(pwd)/test-reports:/app/test-reports \
  blog-helm-ci \
  npm run lint-junit

One small side note regarding user permissions. When running on a Linux server, the test reports will be owned by the root user. This will break the builds, as the build server won’t be able to clean the working directory. There are various ways to solve this, but I find that the least complicated one is to simply fix the permissions manually using our CI image:

docker run \
  -v $(pwd)/test-reports:/app/test-reports \
  blog-helm-ci \
  chown -R $(id -u):$(id -g) test-reports

This will set the correct permissions on the test reports and permit the build server to properly clean up its directories when needed.

There are other options as well, but it complicates the Dockerfile. An alternative that does not complicate code is to use Docker on Docker, which is off topic for this series.

Let’s see how the build looks like in TeamCity:

The build is actually failing because I am using console.log which violates linting:

After I fix this linting issue, I get a green build:

The code is available on GitHub.

On the next post, we’ll have a look at creating a Helm chart for this project.