Как исключить из Docker-монтирования node_modules когда примонтирована родительская по отношению к ним директория?

Директория 03-LocalDevelopmentBuild примонтирована к Docker-контейнеру:

введите сюда описание изображения

Локальная машина производит двухсторонний обмен данными с 03-LocalDevelopmentBuild, тоесть локальная машина собирает проект в эту директорию, а Docker-контейнер хотя большей частью и считывает файлы сборки, но тоже может что-то записать.

введите сюда описание изображения

Насколько мне известно, это вполне естественно для режима локальной разработки, когда как исходные, так и выходные файлы постоянно обновляются, и не тома (volumes), а именно монтирование (bind mount) рекомендовано для таких случаев. Однако в случае с Node.js проблема в том, что из-за очень большого количества файлов и папок в node_modules, а также особенностей реализации мотирования директорий перезапуск приложения занимает слишком много времени (поначалу порядка минуты, что уже делает невозможной комфортную разработку, а по мере роста числа зависимостей может и до нескольких минут увеличиться). Поэтому папку node_modules (которая ниже 03-LocalDevelopmentBuild) хорошо бы исключить из монтирования чтобы она была только внутри контейнера, и ничего страшного, если они пропадут данные при удалении контейнера - это не данные из базы данных. Более того, никаких дополнительных Volume-ов для хранения node_modules мне не нужно. Как исключить из монтирования module_modules?

Повторюсь, что сейчас речь идёт исключительно об организации локальной разработки, а оптимизация при продакшен-сборке - это совсем другой разговор.

Docker.compose.yaml

services:

  FrontServer:

    image: node:22.15-alpine3.20
    container_name: SampleSPA_WithoutSEO-FrontServer-${ENVIRONMENT_UPPER_CAMEL_CASED_NAME}

    working_dir: /var/www/example.com
    command: sh -c "npm run start"

    depends_on:
      - Database

    ports:
      - target: ${FRONT_SERVER_PORT}
        published: ${FRONT_SERVER_PORT}

    volumes:
      - type: bind
        source: 03-LocalDevelopmentBuild
        target: /var/www/example.com
      - type: bind
        source: .env.local.docker.generated
        target: /var/www/example.com/.env

  Database:

    # База данных в проекте есть, но к текущему вопросу отношения не имеет

Дополнительные сведения о проекте

  • Входной и выходной код полностью разделены. Входной код написан на TypeScript, генерируется в выходной JavaScript. (Для сравнения: если код был написан на PHP и Ruby, то там нет разделения на исходных и выходной код, а потому требуется другой подход в организации монтирования)
  • В 03-LocalDevelopmentBuild нет ни одного файла, который добавлен вручную - эта директория на 100% сгенерирована.
  • package.json, который находится в 03-LocalDevelopmentBuild - не тот же самый, что в корне проекта. Если в корневом package.json помимо зависимостей для серверного приложения также упомянуты зависимости только для разработки (devDepencencies) и зависимости для фронтенда, которые Webpack сшивает в один файл, то в package.json ниже 03-LocalDevelopmentBuild упомянуто только то, что нужно для работы серверного приложения. Соответственно в node_modules ниже 03-LocalDevelopmentBuild гораздо меньше зависимостей, чем в тех node_modules, что в корне проекта.

Неработающие либо неприемлемые решения

.dockerignore

Пробовал - в моём случае эффекта нет.

Добавление дополнительного тома для node_modules

services:
    node:
        command: nodemon index.js
        volumes:
            - ./:/usr/local/app/
            # the volume above prevents our host system's node_modules to be mounted
            - exclude:/usr/local/app/node_modules/

volumes:
    exclude:

Как я уже сказал ранее, мне дополнительные тома не нужны (ни именованные, ни анониvные) - мне нужно, чтобы node_modules были внутри контейнера и не маячили в списке томов.

Использование tmpfs mounts

Уже не найду ссылку, но где-то такое рекомендовали. Согласно официальной документации,

As opposed to volumes and bind mounts, a tmpfs mount is temporary, and only persisted in the host memory.

https://docs.docker.com/engine/storage/tmpfs/

Насколько я понял, используется оперативная память. Однако node_modules могут запросто весить больше гигабайта, а потому жертвовать одним гигабайтом оперативки на хранение node_modules считаю расточительным.

Критика

Мне бы очень не хотелось превращать этот вопрос в дискуссию (да и потом, на SO это не приветствуется), но у многих очень велик соблазн сказать: "Ты должен создать отдельный Dockerfile для "FrontServer!"". Что же, объясню почему я категорически не хочу этого делать.

Во-первых, эта рекомендация не является прямым решением данной задачи, а потому является отклонением от темы. Не является прямым решением потому что даёт побочные эффекты. Мне же нужно решение, которое только исключает node_modules из монтирования, но не делает ничего больше.

Во-вторых, мне никто толком не объяснил, почему я это должен сделать. Только абстрактная догматика типа: "так никто не делает, все делают по-другому, потому ты должен так делать иначе будешь плохим".

В третьих, я не хочу создавать Dockerfile только ради четырёх строк наподобие:

FROM node:22.15-alpine3.20
WORKDIR /var/www/example.com
COPY . /var/www/example.com
RUN npm install --no-package-lock

потому что это всё можно в docker-compose.yaml описать. Ну вернее, для режима продакшен у меня такой Dockerfile есть, но:

  • Подразумеваестя, что директория, на которую ссылается точки после COPY ., содержит только выходные файлы сборки, и никаких другие.
  • Для продакшен-сборки нет задачи наблюдения на исходными и выходными файлами, а также выполнением определённых задач при их обновлении

Да и мы сейчас говорим исключительно про режим локальной разработки.

Но даже если Вы мне сформулируете причину, по которой я должен создать такой Dockerfile, то сразу же возникнет вопрос, как быть с обеспечение инкрементальной сборки проекта - ведь файлы, которые скопированы, будут постоянно обновляться (как входные, так и выходные). Собирать проект внутри Docker-контейнера я категорически не буду, потому что сборка средних и крупных проектов с HTML-препроцессорами, CSS-препроцессорами, JavaScript-препроцессорами, изображениями, шрифтами, линтингом и так далее - это очень тяжеловесный процесс, а объём оперативной памяти внутри контейнера сильно ограничен.


Ответы (2 шт):

Автор решения: Alex Wolf

Используйте анонимный volume для node_modules: добавьте путь к поддиректории в volumes, чтобы он перекрыл bind mount для этой папки. Анонимный volume создаётся автоматически, не требует объявления в секции volumes:, и удалится вместе с контейнером.

services:
  FrontServer:
    image: node:22.15-alpine3.20
    container_name: SampleSPA_WithoutSEO-FrontServer-${ENVIRONMENT_UPPER_CAMEL_CASED_NAME}

    working_dir: /var/www/example.com
    command: sh -c "npm run start"

    depends_on:
      - Database

    ports:
      - target: ${FRONT_SERVER_PORT}
        published: ${FRONT_SERVER_PORT}

    volumes:
      - type: bind
        source: 03-LocalDevelopmentBuild
        target: /var/www/example.com
      - type: bind
        source: .env.local.docker.generated
        target: /var/www/example.com/.env
      - /var/www/example.com/node_modules  # Анонимный volume для node_modules

  Database:
    # База данных в проекте есть, но к текущему вопросу отношения не имеет

При первом запуске node_modules будут пустыми, поэтому нужно выполнить npm install внутри контейнера (можно добавить в command или использовать entrypoint). Это анонимный volume: он не появляется в docker volume ls, но фактически создаётся Docker'ом для изоляции node_modules от bind mount. Рекомендуемая команда для первого запуска: command: sh -c "npm install && npm run start"

→ Ссылка
Автор решения: eri

Создай папку внутри контейнера /opt/node_modules . Сделай символьную ссылку внутри проекта ln -s /opt/node_modules ./node_modules

П.С. весь докер контейнер уже лежит в от 1 до нескольких десятков анонимных волумках и ещё одна анонимная волумка создается при старте контейнера (и удаляется при остановке) в неё и пишутся изменения в рантайме. Вообщем записывая что-то внутри контейнера и в отдельную волумку - разницы никакой. Тормоза скорее от того что передаёте папку из виндовс.

tmpfs для node_modules можно если у вас на компе очень много оперативки. С 32ГБ мне было не очень комфортно работать в tmpfs.

→ Ссылка