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
- Your
Dockerfilerunsnpm installwhen building the image - The
node_modulesdirectory (likely a subdirectory of something like/appin the container) gets filled with all the Node packages yourpackage.jsonreferences - 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.
The fix
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 npm, etc.):
yarn- Docker Compose
Assumptions:
- You hold your app code in
/appin the container - Your
docker-compose.yml,Dockerfile, andpackage.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.
- Your
Dockerfilehas its own bespokeCMDorENTRYPOINT, etc.
docker-compose-yml
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.
Dockerfile
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"]
Wrap up
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!
Top comments (0)