There's a gotcha with Docker and Node where the
node_modules directory on the host can override the
node_modules in the container. This typically happens in dev environments, where you want your container to receive updates to files from the host as you work on them.
The way this works:
- You have a Node-based project you'd like to run as a Docker container
npm installwhen building the image
node_modulesdirectory (likely a subdirectory of something like
/appin the container) gets filled with all the Node packages your
- The rest of your Docker image is built
- You ask Docker to run your image as a container, asking it to link the
/appdirectory in the container to the directory of the project on the host.
- If there's a
node_modulesdirectory for the project on the host, it obliterates (or replaces, depending on how you feel about this) the
/app/node_modulesdirectory in the container.
Symptoms of this gotcha include weird dependency version mismatches, missing packages, and exclamations such as 'I updated a dependency but the [redacted] container won't install it!'
If you want to blame something, blame Node for keeping app dependencies in the same directory as the project. There are advantages to this, but there are also disadvantages -- and we're experiencing a big one right here.
A way to fix this is to have your Docker image hold its
node_modules in a different directory. This is easier said than done; as long as your host has a
node_modules directory that gets synced into
/app/node_modules, Node will always prefer it since it's proximate.
The way I've accounted for this preference is to force
/app/node_modules to be empty, regardless of what the host's
node_modules directory contains.
If you're like me, you skipped all the above explanation and scrolled down to this heading. Without further ado, you can fix this issue by updating your project's files with the changes I provide below.
Required tools (for my fix; possibly my fix could be adapted to use
- Docker Compose
- You hold your app code in
/appin the container
package.jsonhold all the other things you need to run your app
- You'll use whichever Node Docker base image you want; I just include my current preference as an example.
Dockerfilehas its own bespoke
version: "3.8" services: web: container_name: my-service hostname: my-service build: context: . # The Dockerfile is in the current directory volumes: - .:/app - /app/node_modules # Ensure `/app/node_modules` is always empty in the container
The final line (
- /app/node_modules ...) is the most important. It ensures that
/app/node_modules is always empty in the container, so that Node ignores it.
FROM node:15.3-alpine3.12 WORKDIR / # Important for installing node packages COPY package.json package.json # Install Node dependencies at `/node_modules` in the container. RUN set -x \ && yarn install --modules-folder=/node_modules ENV PATH=/node_modules/.bin:$PATH WORKDIR /app CMD ["/app/scripts/run.sh"]
Now when your container starts up, your app will use
/node_modules to find dependencies.
You can also run
yarn install and do whatever you want with your
node_modules on the host without worrying about polluting the container's environment. The
/app/node_modules directory in the container will remain empty.
I've tested this with a Next.js project and it worked great. I hope it works for you. Happy programming!