Как исключить из 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.
Насколько я понял, используется оперативная память.
Однако 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 шт):
Используйте анонимный 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"
Создай папку внутри контейнера /opt/node_modules . Сделай символьную ссылку внутри проекта ln -s /opt/node_modules ./node_modules
П.С. весь докер контейнер уже лежит в от 1 до нескольких десятков анонимных волумках и ещё одна анонимная волумка создается при старте контейнера (и удаляется при остановке) в неё и пишутся изменения в рантайме. Вообщем записывая что-то внутри контейнера и в отдельную волумку - разницы никакой. Тормоза скорее от того что передаёте папку из виндовс.
tmpfs для node_modules можно если у вас на компе очень много оперативки. С 32ГБ мне было не очень комфортно работать в tmpfs.

