docker, nodejs

node_modules with bind mounts in Docker

Docker was created to isolate your development environment to suppress any possible issues with different local OSes and configurations. So the idea is that everything is acting the same on every platform. The problems may arise when you run npm i locally and some packages has to be built depending on your system architecture. We want to avoid it and run npm i always in the container.

I will use a simple Node image in this example

FROM node:12.16-slim

WORKDIR /app

COPY package.json package-lock*.json ./

RUN npm install && npm cache clean --force

COPY . .

CMD ["node", "index.js"]
version: '2.4'

services:
  express:
    build: .
    ports:
      - 3000:3000
    volumes:
      - .:/app

Never use npm i on host

If you do not have installed node_modules, you can’t docker compose up until you’ve used docker-compose run npm i. Although it is installed in container, there is mapping .:/app in docker-compose file, so it will be visible from host file system. In case you are using MacOS and in container is Ubuntu or some other OS different than MacOS – those files will me compiled as a source to different architecture. Your app will running ok in a container, but we want to avoid to keep node_modules separate, in case of any doubts that may arise some day, why app is not working as we want.

Another issue may be, when you do npm i on your host, then run docker-compose up and your project probably will be running… until it’s not. It’s analogous to previous example, but this time, we compile some modules on MacOS and bind it to the container, when Ubuntu is running.

Move node_modules in the image to hide it from host

There is feature, which tells that Node will look for node_modules in upper directory directory if it does not find it in current one. We are going to use this here. So, edit the Dockerfile

FROM node:12.16-slim

#add parent directory 
WORKDIR /node

COPY package.json package-lock*.json ./

# install dependencies in /node
RUN npm install && npm cache clean --force

# added new workdir for source files
# node_modules will be in parent folder, 
# hence it will be not visible from host file system
WORKDIR /node/app
COPY . .

CMD ["node", "index.js"]

Then, in docker-compose.yaml

version: '2.4'

services:
  express:
    build: .
    ports:
      - 3000:3000
    volumes:
      # app will be now in subdirectory
      - .:/node/app
      # create anonymous volume
      # map node_modules from host to essentially nothing
      - /app/node_modules