Suppose you don’t want to install Node.js and npm on your local machine and already run your application in a Docker container. How do you install your dependencies from package.json
? For deployment, the most common way is to create your own Docker image, where npm install
is part of the Dockerfile. But what do you do on your development machine, where you don’t want to build a new image all the time and use the vanilla node
image ? Where you want to create a node_modules
directory inside your project directory, with the correct permissions? Jump to the end of the article to get a solution, or follow me on my journey …
First try: running as root
docker run -it --rm -v $(pwd):/app -w /app npm install
A short little command line, that mounts the current directory into the container and runs npm install
as root. It works, but the resulting node_modules
directory will belong to root:root
. Also, npm scripts might throw strange errors or will complain, because npm should not be run as root. If you’re ok with running it as root, you could add the parameter --unsafe-perm
.
Second try: running as unprivileged user
The official Node image comes with the user node
docker run -it --rm -u node -v $(pwd):/app -w /app npm install
This will install the packages, but you still might run into permission problems if the node
user from the docker image (with UID and GID 1000) do not exactly match the UID of the user running the docker command or if the node_modules
directory already exists with different permissions.
Third try: running as the current user
docker run -it --rm -u $(id -u):$(id -g) -v $(pwd):/app -w /app npm install
This will cause npm install
to fail. Judging from the error messages, the cause of failure is that npm tries to run subshells, but there is no entry in /etc/passwd
for the UID and GID you provided. npm will then assume /
as the home directory of that user, failing miserably because the user has no write permissions.
Finally, permission nirvana
A solution for the npm permission problem is to fake the passwd
user entry and its home directory. First, you create a minimal passwd
file for the current user on our host system:
echo "node:x:$(id -u):$(id -g)::/home/node:/bin/bash" > /tmp/fake-passwd
Then you mount the passwd file and a writeable directory of the current user:
docker run -it --rm -u $(id -u):$(id -g) -v /tmp/fake-passwd:/etc/passwd \
-v ~/tmp:/home/node -v $(pwd):/app -w /app npm install
This will install the dependencies with the current user as owner into node_modules
.
Alternative approaches
I don’t like having to create a passwd file and having to mount two additional things, but for my very specific use case - replacing a locally installed npm
with npm
running inside a container from an unmodified Node image - this seems like the best I can do.
I wish Docker would provide a UID mapping between host and container volumes. There once was an issue for that, but the developers decided that the many workarounds would suffice. There is a new issue, with more workarounds, so there is hope that some day Docker might get this functionality.
A very in-depth explanation of such a workaround is “Handling permissions with docker volumes” by Deni Bertovic.
I’m open for alternative suggestions - contact me, if you like.