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
Dockerfile
runsnpm install
when building the image - The
node_modules
directory (likely a subdirectory of something like/app
in the container) gets filled with all the Node packages yourpackage.json
references - The rest of your Docker image is built
- You ask Docker to run your image as a container, asking it to link the
/app
directory in the container to the directory of the project on the host. - If there's a
node_modules
directory for the project on the host, it obliterates (or replaces, depending on how you feel about this) the/app/node_modules
directory 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
/app
in the container - Your
docker-compose.yml
,Dockerfile
, andpackage.json
hold 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
Dockerfile
has its own bespokeCMD
orENTRYPOINT
, 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)