In a previous post, we saw how to dockerize the build plan of an application. Typically, you’ll want the build to run tasks like linting and unit tests, and then publish the results of these operations as XML reports that the build server can consume and present in a human friendly way.

Since these tasks now run within a Docker container, the XML reports will be generated within the container’s filesystem. The build agent can’t access that, unless we use a Docker volume. With the volume, the reports are accessible but they are owned by the root user. This is a problem because the build agent is not running as root (hopefully) and won’t be able to delete these reports when it needs to cleanup (e.g. in order to run the next build in a clean workspace).

The easiest approach is what we did in the post about dockerizing the build plan, simply run the chown command after we use the image, to make sure permissions are back to normal.

Coincidentally, this is exactly what TeamCity does as well (so you don’t need to do it yourself). From the documentation:

After the build step with the Docker wrapper, a build agent will run the chown command to restore access of the buildAgent user to the checkout directory. This mitigates a possible problem when the files from a Docker container are created with the 'root' ownership and cannot be removed by the build agent later.

If your CI server doesn’t support this, you can also try another trick: use a custom entrypoint in the Docker image.

Let’s see the old Dockerfile:

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

Wouldn’t it be great if the permissions got automatically fixed whenever we run a command with this image? We can do that with a custom entrypoint:

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

ENV HOST_USER_ID=
ENV HOST_GROUP_ID=
ADD npm-entrypoint.sh /usr/local/bin/npm-entrypoint.sh
ENTRYPOINT ["/usr/local/bin/npm-entrypoint.sh"]
CMD ["--help"]

And the entrypoint script itself looks like this:

#!/bin/sh
npm run $@
if [ -n "$HOST_USER_ID" -a -n "$HOST_GROUP_ID" ]; then
    chown -R $HOST_USER_ID:$HOST_GROUP_ID .
fi

And using the image changes into this:

docker run --rm \
    -e HOST_USER_ID=$(id -u) \
    -e HOST_GROUP_ID=$(id -g) \
    blog-helm-ci lint

(it used to be just docker run --rm blog-helm-ci npm run lint)

What’s going on? We use the environment variables HOST_USER_ID and HOST_GROUP_ID to pass the user id and group id of the build agent into the Docker container. We retrieve these with $(id -u) and $(id -g).

With the entrypoint, we’re able to run the custom shell script npm-entrypoint.sh upon every usage of the Docker image. The script itself runs npm run with whatever extra arguments we pass and then it corrects the permissions on the current folder, setting the ownership to the user and group of the build agent’s user. This will permit the build agent to clean up these files later.

This is a slightly more complicated approach than just running chown ourselves after using the image, but the advantage is that we don’t need to worry about it, as the image itself now takes care of it. Additionally, we are able to lock down the image so that it only runs npm commands.