Nov 26, 2017

Building inside a Docker container with the correct user

Lately I've been using Docker container as clean, easily portable and easily removable build environments. In those cases the image contains the needed build tools and the project is mounted to a volume inside the container. The artifacts are then built inside the container but are placed inside the volume. However a small problem arises, the artifacts (and whatever other files are created, like cache) are owned by the default user, root, making editing or removing said files less straightforward.

The trivial solution

The trivial solution is to run the container with the correct user id, like so

uid="$(id -u)"
gid="$(id -g)"
docker run -v "$PWD:/volume" --user "$uid:$gid" buildimage make

I personally find it a tiresome after the 3rd time I had to sudo chown the project because I forgot to specify the uid and gid and it's a (low) barrier of entry for new users.

A better solution

The solution I've come up with is this small script that sets the uid and gid values to those of the owner and group for the volume and then execute the commands.

#!/bin/sh
set -eu
[ "$(id -u)" = "0" ] || { echo "Not running as root, continuing as the current user."; eval exec "$@"; }
command -v stat > /dev/null || { echo "Can't find stat, exiting."; exit 1; }
command -v gosu > /dev/null || { echo "Can't find gosu, exiting."; exit 1; }
uid="$(stat . -c '%u')"
gid="$(stat . -c '%g')"
eval exec gosu "$uid:$gid" "$@"

The script is also available for download. The only dependency is gosu. You can download and check it to your VCS and incorporate it into your Dockerfile, or download it via the ADD directive, like so:

FROM buildpack-deps
RUN curl -fsSL https://github.com/tianon/gosu/releases/download/1.10/gosu-amd64 -o gosu-amd64 && \
    install -o root -g root -m 755 gosu-amd64 /usr/local/bin/gosu && \
    rm gosu-amd64 && \
    curl -fsSL https://www.shore.co.il/blog/static/runas -o runas && \
    install -o root -g root -m 755 runas /entrypoint && \
    rm runas
ENTRYPOINT [ "/entrypoint" ]
VOLUME /volume
WORKDIR /volume
ENV HOME /volume

Setting the home directory to the mounted volume will result in some files (like package managers cache) to be created there, which you may or may not want. And then finally, to build run

docker run -v "$PWD:/volume" buildimage make