How to handle node_modules with docker-compose in a monorepo project

Viewed 861

I'm running a Node.js monorepo project using yarn workspaces. File structure looks like this:

workspace_root
    node_modules
    package.json
    apps
        appA
            node_modules
            package.json
        appB
            node_modules
            package.json
    libs
        libA
            dist
            node_modules
            package.json

All apps are independents, but they all require libA

I'm running all these apps with docker-compose. My question here is how to handle properly all the dependencies as I don't want the node_modules folders to be synchronized with host. Locally, when I run yarn install at workspace root, it installs all dependencies for all projects, populating the different node_modules. In docker-compose, ideally each app should not be aware of others apps.

My approach so far, which is working but not ideal and not very scalable.

version: "3.4"

services:
  # The core is in charge of installing dependencies for ALL services. Each service must for wait the core, and then
  # just do their job, not having to handle install.
  appA:
    image: node:14-alpine
    volumes: # We must load every volumes for install
        - .:/app  # Mount the whole workspace structure
        - root_node_modules:/app/node_modules
        - appA_node_modules:/app/apps/appA/node_modules
        - appB_node_modules:/app/apps/appB/node_modules
        - libA_node_modules:/app/libs/libA/node_modules
    working_dir: /app/apps/appA
    command: [sh, -c, "yarn install && yarn run start"]

  appB:
    image: node:14-alpine
    volumes: # We must load every volumes for install
        - .:/app  # Mount the whole workspace structure
        - root_node_modules:/app/node_modules
        - appB_node_modules:/app/apps/appB/node_modules
    working_dir: /app/apps/appB
    command: [sh, -c, "/scripts/wait-for-it.sh appA:4001  -- yarn run start"]

    # And so on for all apps....
  
volumes:
    root_node_modules:
        driver: local
    appA_node_modules:
        driver: local
    appB_node_modules:
        driver: local
    libA_node_modules:
        driver: local

The main drawbacks I see:

  • Service appA is responsible for install dependencies of ALL apps.
  • I have to create a volume for each app + one for the root node_modules
  • The whole project is mounted in each service, even though I'm using only a specific folder

I would like to avoid a build for development, as it has to be done each time you add a dependency, it's quite cumbersome and it's slowing you down

1 Answers

attaching at the bottom of this answer example repository i have created.

Basically utilizing yarn workspaces i have created a common dockerfile for each of the packages/modules to use when built.

The entire repository is copied for each of the docker images (It's not a good practices for later releasing the product, you would probably want to create a different flow for that)

So if the entire repository is mounted to each of the running services you can watch changes in the libraries (in the repository i have configured nodemon so it will also watch the lib files)

To sum this up:

  1. Hot reload even if libraries are changing because the entire project is mounted to each of the services docker containers
  2. utilizing yarn workspaces to manage the packages easily with convenience commands
  3. For building each of the libraries each time they change they should have each respectively a docker container raised by the docker-compose
  4. Development process is not a good practice for any production related processes like releasing the docker images later since all of the repository is available in the image
  5. Once added the libraries as docker service each with hot reload they will be rebuilt every-time you make a change so no need to docker-compose build repeatedly.

Anyways i would have not worried much about the repeated docker-compose build once the libraries are settled and changes are less frequent you will find your self less rebuilding (But any ways i gave the solution for that also)

Github Repository example

Related