DEV Community

Sean Gilbertson
Sean Gilbertson

Posted on

How to create a Dockerized Node application with separate node_modules for container and host

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:

  1. You have a Node-based project you'd like to run as a Docker container
  2. Your Dockerfile runs npm install when building the image
  3. The node_modules directory (likely a subdirectory of something like /app in the container) gets filled with all the Node packages your package.json references
  4. The rest of your Docker image is built
  5. 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.
  6. 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, and package.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 bespoke CMD or ENTRYPOINT, 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
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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)