Docker: Installing npm packages from github

Building Node.js applications within a docker container, e.g. based on the official node docker image, is a great idea as it allows you to bundle all of your requirements along with the specific version of node into a docker image that can easily be used by others to develop on the same (not just similar!) stack and can even be used to run the app in production in the end. This provides for a very smooth way to achieve dev / prod parity.

If you’re just getting started with developing Node.js apps within docker you might want to check out good docker defaults.

When building Node.js applications, eventually you may at some point end up needing to install a package that is not yet published to the npm registry. This can e.g. happen when you are developing this package in parallel or don’t want to make it public (yet).

Install git package via npm

According to npm’s docs packages can also be installed from a git repository and it even provides a shorthand syntax to install packages which reside on GitHub. The syntax is straightforward enough, you can run:

npm install git+https://example.org/my-org/my-repo.git#master

to install from a generic git repository or use the shorthandle

npm install my-org/my-repo#master

if the repository is hosted on GitHub.

Failing NPM scripts

For simple npm packages, everything is fine until your package contains an npm script like postInstall or prepare that needs to be run at the time of the package being installed, e.g. because the package’s source code needs to be transpiled using babel, a module bundler like webpack needs to perform some work, a task runner like grunt or gulp performs some action or a native module has to be re-compiled for the current platfom.

package.json:

{
    // ...
    "name": "My amazing package",
    "scripts": {
        "prepare": "babel src",
    },
    // ...
}

This works fine as long as you run it on your machine, but as soon as you try to install the package within your Node.js-based docker container, suddenly, the npm install command seems to go through, but once you check the respective folder within node_modules, you don’t see your transpiled / built files anymore.

If in the past you already had issues with npm, you might know about the command line argument to change the loglevel but in case you have not, adding -ddd to your command outputs a ton of additional information. If you carefully check these verbose logs, you’ll find a line during the installation process stating:

npm verb prepareGitDep undefined: installing devDeps and running prepare script.
[...]
npm sill prepareGitDep npm WARN lifecycle [...]~prepare: cannot run in wd [...] (wd=/root/.npm/_cacache/tmp/git-clone-c0ff03b8)

This seemingly “silly” warning informing you that the prepare step cannot be un in the wd (working directory) means you app will not work as the mandatory postinstall / prepare scripts will not have been successfully run.

Docker Node Image & Permissions

The problem becomes apparent when inspecting the path of the working directory in which the prepare steps cannot be run. The working directory (in our example above: /root/.npm/_cacache/tmp/git-clone-c0ff03b8) resides under the root user’s folder (because the default user within the docker image is root) but npm changes to user nobody prior to running the prepare scripts and since nobody does not have access to root’s files, we get the above warning.

Solutions

Now that we know about the permission problem, what can we do to fix it or workaround it.

Publish to npm registry

If you are confident about putting your package out there, publishing it to the npm registry is a simple solution to the problem. During the publish procedure, the prepare / postinstall steps will be run and once someone installs the package from the registry, those steps do not need to be run anymore as the published version already contains the built result.

Use --unsafe-perm argument

Not running npm scripts as the root user is a security measurement imposed by npm to make sure that potentially harmful npm packages cannot simply gain root access to your system. A wise decision! If, however, you’re feeling absolutely confident about the package you want to install (and its respective npm scripts), you can simply append --unsafe-perm to the npm install command and the scripts will be run as root:

npm install --unsafe-perm my-org/my-repo#master

Change the user

The best solution in my eyes would be to just switch to a unprivileged user prior to installing any npm packages, then switch back to the root user (if you must) afterwards. Running as root is discouraged anyways and with docker it’s just changing your Dockerfile like the following or using the -u / --user command line argument on docker run / docker exec commands:

Dockerfile:

FROM node:stable-slim

# switch to unprivileged node user
USER node
# install npm dependencies
RUN npm install --unsafe-perm my-org/my-repo#master
# optional: switch back to root user
# USER root

# ...