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

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

  • web/ - хранит и запускает веб-сервер на флэке python 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 [email protected] No README data

> [email protected] 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.)

Ответ 1

Это происходит потому, что вы добавили каталог 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

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

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

Ответ 2

Папка 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 недоступен извне контейнера, потому что он включен в изображение.

Ответ 3

Решение, предоставляемое @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

Ответ 4

У меня недавно была аналогичная проблема. Вы можете установить 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

Ответ 5

Там элегантное решение:

Просто установите не весь каталог, а только каталог приложения. Таким образом, у вас не будет проблем с 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

Ответ 6

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

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

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

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

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

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

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

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

Ответ 7

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

Ответ 8

Установка 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

Ответ 9

Существует также некоторое простое решение без отображения каталога 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

Ответ 10

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

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

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

Второй - на самом деле сложнее. Мое решение немного хакерское, но оно работает. У меня есть script, чтобы установить папку 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

Ответ 11

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

docker run -it -v ./app:/usr/src/app  your_node_image_name  /bin/bash
[email protected]:/usr/src/app# npm install

Ответ 12

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

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

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

volumes:
  - worker/:/worker:rw

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

#!/bin/sh
npm install
npm start

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