Docker-compose: node_modules отсутствуют в томе после успешной установки npm

184

У меня есть приложение со следующими услугами:

  • web/ - удерживает и запускает веб-сервер с питоном 3 на порту 5000. Использует sqlite3.
  • worker/- есть index.jsфайл, который является работником для очереди. веб-сервер взаимодействует с этой очередью, используя json API через порт 9730. Работник использует Redis для хранения. Рабочий также хранит данные локально в папкеworker/images/

Теперь этот вопрос касается только worker.

worker/Dockerfile

FROM node:0.12

WORKDIR /worker

COPY package.json /worker/
RUN npm install

COPY . /worker/

docker-compose.yml

redis:
    image: redis
worker:
    build: ./worker
    command: npm start
    ports:
        - "9730:9730"
    volumes:
        - worker/:/worker/
    links:
        - redis

Когда я запускаю docker-compose build, все работает как положено, и все модули npm устанавливаются так, /worker/node_modulesкак я ожидал.

npm WARN package.json unfold@1.0.0 No README data

> phantomjs@1.9.2-6 install /worker/node_modules/pageres/node_modules/screenshot-stream/node_modules/phantom-bridge/node_modules/phantomjs
> node install.js

<snip>

Но когда я это docker-compose upвижу, я вижу эту ошибку:

worker_1 | Error: Cannot find module 'async'
worker_1 |     at Function.Module._resolveFilename (module.js:336:15)
worker_1 |     at Function.Module._load (module.js:278:25)
worker_1 |     at Module.require (module.js:365:17)
worker_1 |     at require (module.js:384:17)
worker_1 |     at Object.<anonymous> (/worker/index.js:1:75)
worker_1 |     at Module._compile (module.js:460:26)
worker_1 |     at Object.Module._extensions..js (module.js:478:10)
worker_1 |     at Module.load (module.js:355:32)
worker_1 |     at Function.Module._load (module.js:310:12)
worker_1 |     at Function.Module.runMain (module.js:501:10)

Оказывается, ни один из модулей не присутствует в /worker/node_modules(на хосте или в контейнере).

Если на хосте я npm install, то все работает просто отлично. Но я не хочу этого делать. Я хочу, чтобы контейнер обрабатывал зависимости.

Что здесь не так?

(Излишне говорить, что все пакеты в package.json.)

KGO
источник
Вы в конечном итоге нашли решение?
Джастин Стейтон
Я думаю, что вы должны использовать инструкцию ONBUILD ... Как это: github.com/nodejs/docker-node/blob/master/0.12/onbuild/…
Лукас Поттерский
1
Как бы вы занялись разработкой на хосте, если IDE не знает зависимостей node_module?
Андре
2
Попробуйте удалить volumes: - worker/:/worker/блок из docker-compose.ymlфайла. Эта строка перезаписывает папку, созданную командой COPY.
Степан
When I run docker-compose build, everything works as expected and all npm modules are installed in /worker/node_modules as I'd expect.- Как ты это проверил?
Валли

Ответы:

272

Это происходит потому, что вы добавили свой workerкаталог в качестве тома в свой docker-compose.yml, так как том не смонтирован во время сборки.

Когда docker создает образ, node_modulesкаталог создается внутри workerкаталога, и там устанавливаются все зависимости. Затем во время выполнения workerкаталог из внешнего докера монтируется в экземпляр докера (на котором нет установленного node_modules), скрывая node_modulesтолько что установленный вами. Вы можете проверить это, удалив подключенный том из вашего docker-compose.yml.

Обходной путь - использовать том данных для хранения всех node_modulesданных, так как тома данных копируют данные из встроенного образа докера до workerмонтирования каталога. Это можно сделать docker-compose.ymlтак:

redis:
    image: redis
worker:
    build: ./worker
    command: npm start
    ports:
        - "9730:9730"
    volumes:
        - ./worker/:/worker/
        - /worker/node_modules
    links:
        - redis

Я не совсем уверен, накладывает ли это какие-либо проблемы на переносимость образа, но, поскольку кажется, что вы в первую очередь используете docker для обеспечения среды выполнения, это не должно быть проблемой.

Если вы хотите узнать больше о томах, есть хорошее руководство пользователя, доступное здесь: https://docs.docker.com/userguide/dockervolumes/

РЕДАКТИРОВАТЬ: С тех пор Docker изменил свой синтаксис, требуя ./указания для монтирования в файлах относительно файла docker-compose.yml.

FrederikNS
источник
7
отличный ответ, наименее навязчивый и отличное объяснение!
kkemple
41
Я попробовал этот метод и ударил стену, когда зависимости изменились. Я перестроил образ, запустил новый контейнер и объем /worker/node_modulesостался прежним (со старыми зависимостями). Есть ли хитрость, как использовать новый объем при восстановлении изображения?
Ондрей Слинтак
11
Кажется, что docker compose не удаляет тома, если их используют другие контейнеры (даже если они мертвы). Поэтому, если есть несколько мертвых контейнеров того же типа (по любой причине), сценарий, который я описал в предыдущем комментарии, следует. docker-compose rmСудя по тому, что я пробовал, использование, кажется, решает эту проблему, но я считаю, что должно быть лучшее и более простое решение.
Ондрей Слинтак
15
Есть ли решение в 2018 году без необходимости rebuild --no-cacheкаждый раз менять дек?
Eelke
7
теперь вы можете использовать `--renew-anon-volume`, который будет воссоздавать анонимные тома вместо данных из предыдущих контейнеров.
Мохаммед Эссехеми
37

node_modulesПапка перезаписывается по объему и не более доступными в контейнере. Я использую встроенную стратегию загрузки модулей, чтобы вынуть папку из тома:

/data/node_modules/ # dependencies installed here
/data/app/ # code base

Dockerfile:

COPY package.json /data/
WORKDIR /data/
RUN npm install
ENV PATH /data/node_modules/.bin:$PATH

COPY . /data/app/
WORKDIR /data/app/

node_modulesКаталог не доступен снаружи контейнера , так как он входит в образ.

JSAN
источник
1
Есть ли недостатки этого подхода? Кажется, работает хорошо для меня.
Брет Фишер
1
node_modulesнедоступен снаружи контейнера, но на самом деле не является недостатком;)
jsan
и каждый раз, когда вы меняете package.json, вам нужно перестраивать весь контейнер с ключом --no-cache, верно?
Лука
Вам нужно перестроить образ при изменении package.json, да, но --no-cache не требуется. Если вы запустите, docker-compose run app npm installвы создадите node_modules в текущем каталоге, и вам больше не нужно перестраивать образ.
19
9
Недостатком является отсутствие автодополнения IDE, никакой помощи, хорошего опыта разработчиков. Все также должно быть установлено на хосте сейчас, но разве здесь нет причины использовать докер, что dev-host ничего не нужно для работы с проектом?
Майкл Б.
31

Решение, предоставленное @FrederikNS, работает, но я предпочитаю явно называть мой том node_modules.

Мой project/docker-compose.ymlфайл (docker-compose version 1.6+):

version: '2'
services:
  frontend:
    ....
    build: ./worker
    volumes:
      - ./worker:/worker
      - node_modules:/worker/node_modules
    ....
volumes:
  node_modules:

моя файловая структура:

project/
   │── worker/
        └─ Dockerfile
   └── docker-compose.yml

Он создает именованный том project_node_modulesи использует его каждый раз, когда я запускаю свое приложение.

Моя docker volume lsвыглядит так:

DRIVER              VOLUME NAME
local               project1_mysql
local               project1_node_modules
local               project2_postgresql
local               project2_node_modules
Гийом Винсент
источник
3
хотя это «работает», вы обходите реальную концепцию в докере: все зависимости должны быть встроены для максимальной переносимости. Вы не можете переместить это изображение, не выполняя другие команды, отчасти ударяющие
Хавьер Буззи
4
пытался решить ту же проблему часами и придумал то же решение сам. Ваш ответ должен иметь самый высокий рейтинг, но я думаю, что ppl не получит ваш ответ, потому что вы назвали свой том "node_modules", и всем читателям не хватает того факта, что это создает новый том. При чтении вашего кода я подумал, что вы просто «монтируете» папку locale node_modules, и отказался от этой идеи. Возможно, вам следует изменить имя тома на что-то вроде «container_node_modules», чтобы это было понятно. :)
Фабиан
1
Я также обнаружил, что это наиболее элегантное решение, которое позволяет легко ссылаться на один и тот же том для узловых модулей на нескольких этапах сборки. В отличной статье « Уроки создания приложений Node в Docker» также используется тот же подход.
kf06925
1
@ kf06925 человек, которого ты действительно спас меня, я потратил часы, пытаясь решить эту проблему, и благодаря статье я смог !! Я бы купил тебе пиво, если бы мог большое спасибо
helado
21

У меня недавно была похожая проблема. Вы можете установить в node_modulesдругом месте и установить NODE_PATHпеременную среды.

В приведенном ниже примере я установил node_modulesв/install

работник / Dockerfile

FROM node:0.12

RUN ["mkdir", "/install"]

ADD ["./package.json", "/install"]
WORKDIR /install
RUN npm install --verbose
ENV NODE_PATH=/install/node_modules

WORKDIR /worker

COPY . /worker/

докер-compose.yml

redis:
    image: redis
worker:
    build: ./worker
    command: npm start
    ports:
        - "9730:9730"
    volumes:
        - worker/:/worker/
    links:
        - redis
ericstolten
источник
6
Решение @FrederikNS, получившее наибольшее количество голосов, было полезно, поскольку я решил другую проблему, связанную с перезаписью контейнера локальным томом на node_modulesоснове этой статьи . Но это привело меня к проблеме . Это решение, заключающееся в создании отдельного каталога для копирования package.jsonв него, его запуска npm install, а затем указания NODE_PATHпеременной среды, docker-compose.ymlуказывающей на node_modulesпапку этого каталога, работает и чувствует себя хорошо.
cwnewhouse
Включение ENV NODE_PATH = / install / node_modules в Dockerfile было, наконец, решением для меня после нескольких часов попыток различных подходов. Спасибо, сэр.
Бенни Мид
Что делать, если вы работаете npm installна хосте? Похоже node_modules, появится на хосте и будет отражен в контейнере, принимая приоритет над NODE_PATH. Таким образом, контейнер будет использовать node_modules от хоста.
Виталец
19

Есть элегантное решение:

Просто смонтируйте не весь каталог, а только каталог приложения. Таким образом, у вас не будет проблем сnpm_modules .

Пример:

  frontend:
    build:
      context: ./ui_frontend
      dockerfile: Dockerfile.dev
    ports:
    - 3000:3000
    volumes:
    - ./ui_frontend/src:/frontend/src

Dockerfile.dev:

FROM node:7.2.0

#Show colors in docker terminal
ENV COMPOSE_HTTP_TIMEOUT=50000
ENV TERM="xterm-256color"

COPY . /frontend
WORKDIR /frontend
RUN npm install update
RUN npm install --global typescript
RUN npm install --global webpack
RUN npm install --global webpack-dev-server
RUN npm install --global karma protractor
RUN npm install
CMD npm run server:dev
Holms
источник
Brilliant. Не могу понять, почему это не принятый ответ.
Дживан
2
Это хорошее и быстрое решение, но оно требует перестройки и удаления после установки новой зависимости.
Кунок
Теперь я делаю это по-другому, должен быть том, специально предназначенный для node_modules, и том, который вы монтируете с хоста. Тогда нет никаких проблем
Holms
14

ОБНОВЛЕНИЕ: используйте решение, предоставленное @FrederikNS.

Я столкнулся с той же проблемой. Когда папка/worker подключена к контейнеру - все ее содержимое будет синхронизировано (поэтому папка node_modules исчезнет, ​​если у вас ее нет локально).

Из-за несовместимых пакетов npm, основанных на ОС, я не мог просто установить модули локально - затем запустить контейнер, так что ..

Мое решение для этого было заключить в исходный код в srcпапку, а затем создать ссылку node_modulesна эту папку, используя этот файл index.js . Итак, index.jsфайл теперь является отправной точкой моего приложения.

Когда я запускаю контейнер, я монтировал /app/srcпапку в свой локальныйsrc папку.

Итак, папка контейнера выглядит примерно так:

/app
  /node_modules
  /src
    /node_modules -> ../node_modules
    /app.js
  /index.js

Это некрасиво , но это работает ..

ВАРЕНЬЕ
источник
3
о, дорогой лорд ... я не могу поверить, что я застрял с этим!
Лукас Поттерский
10

Благодаря тому, как Node.js загружает модули , он node_modulesможет находиться где угодно на пути к вашему исходному коду. Например, поместите источник в /worker/srcи ваш package.jsonин /worker, так /worker/node_modules, где они установлены.

Джастин Стейтон
источник
7

Установка node_modules в контейнере, отличном от папки проекта, и установка NODE_PATH в папку node_modules мне помогают (вам нужно перестроить контейнер).

Я использую docker-compose. Структура моего проекта:

-/myproject
--docker-compose.yml
--nodejs/
----Dockerfile

докер-compose.yml:

version: '2'
services:
  nodejs:
    image: myproject/nodejs
    build: ./nodejs/.
    volumes:
      - ./nodejs:/workdir
    ports:
      - "23005:3000"
    command: npm run server

Dockerfile в папке nodejs:

FROM node:argon
RUN mkdir /workdir
COPY ./package.json /workdir/.
RUN mkdir /data
RUN ln -s /workdir/package.json /data/.
WORKDIR /data
RUN npm install
ENV NODE_PATH /data/node_modules/
WORKDIR /workdir
sergeysynergy
источник
1
Это лучшее решение, которое я нашел. NODE_PATHбыл ключом для меня.
cdignam
Я думаю, что это имеет смысл, но установка NODE_PATH при запуске образа CMD npm start не использует указанный NODE_PATH.
Актон
6

Существует также простое решение без отображения node_moduleкаталога в другой том. Это собирается перенести установку пакетов npm в последнюю команду CMD.

Недостаток этого подхода:

  • запускать npm installкаждый раз при запуске контейнера (переключение с npmна yarnтакже может немного ускорить этот процесс).

работник / Dockerfile

FROM node:0.12
WORKDIR /worker
COPY package.json /worker/
COPY . /worker/
CMD /bin/bash -c 'npm install; npm start'

докер-compose.yml

redis:
    image: redis
worker:
    build: ./worker
    ports:
        - "9730:9730"
    volumes:
        - worker/:/worker/
    links:
        - redis
Эгель
источник
3

Есть два отдельных требования, которые я вижу для сред разработки узлов: смонтируйте ваш исходный код в контейнер и смонтируйте node_modules FROM из контейнера (для вашей IDE). Чтобы выполнить первое, вы делаете обычное монтирование, но не все ... только то, что вам нужно

volumes:
    - worker/src:/worker/src
    - worker/package.json:/worker/package.json
    - etc...

(причина не делать - /worker/node_modules том, что docker-compose сохранит этот объем между запусками, а это означает, что вы можете отличаться от того, что на самом деле находится в образе (отказываясь от цели не просто привязки к вашему хосту)).

Второй на самом деле сложнее. Мое решение немного хакерское, но оно работает. У меня есть скрипт для установки папки node_modules на моем хост-компьютере, и я просто должен помнить, чтобы вызывать его всякий раз, когда обновляю package.json (или добавляю его в цель make, которая выполняет сборку docker-compose локально).

install_node_modules:
    docker build -t building .
    docker run -v `pwd`/node_modules:/app/node_modules building npm install
Пол Бекотт
источник
2

На мой взгляд, мы не должны RUN npm installв Dockerfile. Вместо этого мы можем запустить контейнер, используя bash для установки зависимостей перед запуском службы формального узла

docker run -it -v ./app:/usr/src/app  your_node_image_name  /bin/bash
root@247543a930d6:/usr/src/app# npm install
саламандра
источник
Я действительно согласен с вами в этом. Тома предназначены для использования, когда вы хотите поделиться данными между контейнером и хостом. Когда вы решите сохранить свою node_modulesстойкость даже после удаления контейнера, вы также должны знать, когда или когда не следует делать это npm installвручную. ОП предлагает делать это при каждой сборке образа . Вы можете сделать это, но вам не нужно также использовать объем для этого. На каждой сборке модули будут обновлены в любом случае.
phil294
@Blauhirn полезно монтировать том локального хоста в контейнер при выполнении, например, gulp watch (или аналогичных команд) - вы хотите, чтобы node_modules сохранялись, в то же время позволяя вносить изменения в другие источники (js, css и т. Д.). npm настаивает на использовании локального глотка, поэтому он должен сохраняться (или быть установленным другими способами при запуске)
tbm
2

Вы можете попробовать что-то вроде этого в вашем Dockerfile:

FROM node:0.12
WORKDIR /worker
CMD bash ./start.sh

Тогда вы должны использовать объем следующим образом:

volumes:
  - worker/:/worker:rw

Стартовый скрипт должен быть частью вашего рабочего репозитория и выглядит так:

#!/bin/sh
npm install
npm start

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

Parav01d
источник
Это добавит большие накладные расходы для запуска контейнера.
TBM
2
Но только в первый раз, потому что node_modules будут сохранены на локальной машине.
Parav01d
Или пока изображение не будет перестроено, или громкость не будет удалена :). Тем не менее, я не нашел лучшего решения сам.
TBM
0

Вы также можете отказаться от своего Dockerfile, из-за его простоты, просто используйте базовое изображение и укажите команду в вашем файле compose:

version: '3.2'

services:
  frontend:
    image: node:12-alpine
    volumes:
      - ./frontend/:/app/
    command: sh -c "cd /app/ && yarn && yarn run start"
    expose: [8080]
    ports:
      - 8080:4200

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

itmuckel
источник