Correct file ownership when mounting local folders in Docker on Linux.

I just joined a new company. Our local development happens by launching multiple containers via docker compose. Most of the existing developers are using Mac OS and Docker Desktop. This means mounting local folders into a container does not result in the same behaviour as doing so on Linux, which is my operating system of choice.

When onboarding, I ran into a peculiar problem where the frontend container based on node:17.4-alpine would bork with EACCES: permission denied.

npm WARN logfile Error: EACCES: permission denied, scandir '/root/.npm/_logs'
npm WARN logfile  error cleaning log files [Error: EACCES: permission denied, scandir '/root/.npm/_logs'] {
npm WARN logfile   errno: -13,
npm WARN logfile   code: 'EACCES',
npm WARN logfile   syscall: 'scandir',
npm WARN logfile   path: '/root/.npm/_logs'

StackOverflow provided an answer as to why this is happening. It boils down to running npm as root requires the folder which you run npm in needs to be owned by root as well. This wasn’t a workable solution, as I didn’t want to start to run sudo with everything (security issues notwithstanding).

Apart from the npm issue preventing the container from running, having my local directory structure filled with files and folders owned by root was not something I was going to enjoy.

The solution was Docker build arguments, passing my local user id, group id as well as the value of Bash’s built-in $OSTYPE environment variable.

# As part of a Bash script
docker compose -f foo.yml build \
    --build-arg USER_ID="$(id -u)" \
    --build-arg GROUP_ID="$(id -g)" \
    --build-arg HOSTOSTYPE="$OSTYPE"

OSTYPE is set to linux-gnu in Bash on my machine (running Pop_OS! 21.10, a Ubuntu derivative).

The Dockerfile had to be amended as well, using Alpine’s shadow package to provide usermod and groupmod to set the existing node user’s user id and group id to that of the host user’s values.

FROM node:17.4-alpine

RUN apk add shadow

# Container needs to run as non-privileged user, or on Linux permissions will result in EACCES error in npm
# See https://stackoverflow.com/questions/70952903/npm-error-eacces-permission-denied-scandir
ARG GROUP_ID
ARG USER_ID
ARG HOSTOSTYPE

# Make sure container and host UID/GID match on Linux
RUN if [[ "$HOSTOSTYPE" == "linux-gnu"* ]]; then usermod -u $USER_ID node && groupmod -g $GROUP_ID node; fi

USER node

# Everything from here is then run by the `node` user, using the host machine's user and group id on Linux

WORKDIR /foo

RUN npm install

The above solution helped me fix the EACCES issue as well as have files created with the right ownership. It did not break Mac compatibility either.