Docker-compose: moduły_węzła nie są obecne w woluminie po pomyślnej instalacji npm

184

Mam aplikację z następującymi usługami:

  • web/ - przechowuje i uruchamia serwer WWW z kolbą Pythona 3 na porcie 5000. Używa sqlite3.
  • worker/- ma index.jsplik, który jest robotem dla kolejki. serwer WWW wchodzi w interakcję z tą kolejką za pomocą interfejsu API json przez port 9730. Pracownik używa Redis do przechowywania. Pracownik przechowuje również dane lokalnie w folderzeworker/images/

Teraz to pytanie dotyczy tylko 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

Po uruchomieniu docker-compose buildwszystko działa zgodnie z oczekiwaniami, a wszystkie moduły npm są instalowane /worker/node_moduleszgodnie z oczekiwaniami.

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>

Ale kiedy to robię docker-compose up, widzę ten błąd:

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)

Okazuje się, że żaden z modułów nie jest obecny /worker/node_modules(na hoście lub w kontenerze).

Jeśli na hoście, ja npm install, wtedy wszystko działa dobrze. Ale nie chcę tego robić. Chcę, aby kontener obsługiwał zależności.

Co tu idzie nie tak?

(Nie trzeba dodawać, że wszystkie paczki są w package.jsonśrodku).

KGo
źródło
Czy znalazłeś rozwiązanie?
Justin Stayton,
Myślę, że powinieneś użyć instrukcji ONBUILD ... W ten sposób: github.com/nodejs/docker-node/blob/master/0.12/onbuild/...
Lucas
1
Jak zrobiłbyś programowanie na hoście, gdy IDE nie zna zależności modułu_węzła?
André
2
Spróbuj usunąć volumes: - worker/:/worker/blok z docker-compose.ymlpliku. Ten wiersz nadpisuje folder utworzony za pomocą polecenia KOPIUJ.
Stepan
When I run docker-compose build, everything works as expected and all npm modules are installed in /worker/node_modules as I'd expect.- Jak to sprawdziłeś?
Vallie

Odpowiedzi:

272

Dzieje się tak, ponieważ workerkatalog został dodany do woluminu docker-compose.yml, ponieważ wolumin nie jest montowany podczas kompilacji.

Podczas dokowania kompiluje obraz, node_moduleskatalog jest tworzony w tym workerkatalogu i wszystkie zależności są tam instalowane. Następnie w czasie wykonywania workerkatalog z zewnętrznego okna dokowanego jest montowany w instancji dokera (która nie ma zainstalowanej node_modules), ukrywając node_moduleswłaśnie zainstalowaną. Możesz to sprawdzić, usuwając zamontowany wolumin z twojego docker-compose.yml.

Obejściem tego problemu jest użycie woluminu danych do przechowywania wszystkich node_modules, ponieważ woluminy danych kopiują dane z wbudowanego obrazu dokera przed workerzamontowaniem katalogu. Można to zrobić w docker-compose.ymlnastępujący sposób:

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

Nie jestem do końca pewien, czy powoduje to jakiekolwiek problemy z przenośnością obrazu, ale ponieważ wydaje się, że przede wszystkim używasz dockera do zapewnienia środowiska wykonawczego, nie powinno to stanowić problemu.

Jeśli chcesz dowiedzieć się więcej o woluminach, tutaj znajduje się miły przewodnik użytkownika: https://docs.docker.com/userguide/dockervolumes/

EDYCJA: Od tego czasu Docker zmienił swoją składnię, aby wymagać wiodącego sposobu ./montowania w plikach względem pliku docker-compose.yml.

FrederikNS
źródło
7
świetna odpowiedź, najmniej inwazyjne i świetne wyjaśnienie!
kkemple,
41
Wypróbowałem tę metodę i uderzyłem w ścianę, gdy zmieniły się zależności. Odbudowałem obraz, uruchomiłem nowy kontener, a wolumen /worker/node_modulespozostał taki sam jak poprzednio (ze starymi zależnościami). Czy jest jakiś sposób na użycie nowego woluminu po odbudowaniu obrazu?
Ondrej Slinták
11
Wygląda na to, że funkcja dokowania nie usuwa woluminów, jeśli używają ich inne pojemniki (nawet jeśli są martwe). Więc jeśli są jakieś martwe pojemniki tego samego typu (z jakiegokolwiek powodu), scenariusz opisany w poprzednim komentarzu jest następujący. Z tego, co próbowałem, używanie docker-compose rmwydaje się naprawiać ten problem, ale uważam, że musi być lepsze i łatwiejsze rozwiązanie.
Ondrej Slinták
15
Czy istnieje rozwiązanie w 2018 roku bez konieczności rebuild --no-cachekażdorazowej zmiany deps?
Eelke
7
możesz teraz użyć `--renew-anon-Volume`, który odtworzy anonimowe woluminy zamiast danych z poprzednich kontenerów.
Mohammed Essehemy
37

node_modulesFolder jest zastępowane przez objętość i nie bardziej dostępnym w pojemniku. Używam natywnej strategii ładowania modułu, aby wyjąć folder z woluminu:

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

Plik Docker:

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

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

node_modulesKatalog nie jest dostępny z zewnątrz zbiornika, ponieważ znajduje się w obrazie.

jsan
źródło
1
czy są jakieś wady tego podejścia? Wydaje się, że działa dobrze dla mnie.
Bret Fisher
1
node_modulesnie jest dostępny z zewnątrz kontenera, ale tak naprawdę nie jest wadą;)
jsan
i za każdym razem, gdy zmieniasz pakiet.json, musisz przebudować cały kontener z opcją --no-cache, prawda?
Łukasz
Musisz przebudować obraz po zmianie pliku package.json, tak, ale --no-cache nie jest konieczne. Po uruchomieniu docker-compose run app npm installutworzysz moduły_węzła w bieżącym katalogu i nie musisz już odbudowywać obrazu.
jan
9
Minusem jest: brak autouzupełniania IDE, brak pomocy, brak dobrych doświadczeń programistów. Wszystko musi być teraz zainstalowane na hoście, ale czy nie jest powód, aby używać tutaj dokera, że ​​dev-host nie potrzebuje niczego, aby móc pracować z projektem?
Michael B.,
31

Rozwiązanie dostarczone przez @FrederikNS działa, ale wolę jawnie nazwać wolumin mój moduł_węzła.

Mój project/docker-compose.ymlplik (wersja dokowana-wersja 1.6+):

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

moja struktura plików to:

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

Tworzy wolumin o nazwie project_node_modules i używa go ponownie przy każdym uruchomieniu aplikacji.

Mój docker volume lswygląda tak:

DRIVER              VOLUME NAME
local               project1_mysql
local               project1_node_modules
local               project2_postgresql
local               project2_node_modules
Guillaume Vincent
źródło
3
podczas gdy to „działa”, omijasz prawdziwą koncepcję w oknie dokowanym: wszystkie zależności powinny być wprowadzone w celu zapewnienia maksymalnej przenośności. nie można przenieść tego obrazu bez uruchomienia innych poleceń, które w pewnym sensie
Javier Buzzi
4
próbowałem rozwiązać ten sam problem przez wiele godzin i wymyśliłem to samo rozwiązanie dla siebie. Twoja odpowiedź powinna być najwyżej oceniana, ale myślę, że ppl nie dostanie twojej odpowiedzi, ponieważ nazwa twojego wolumenu to „node_modules”, a wszyscy czytelnicy pomijają fakt, że tworzy to nowy wolumin. Czytając kod, pomyślałem, że po prostu „ponownie montujesz” folder ustawień regionalnych node_modules i odrzuciłeś ten pomysł. Być może powinieneś zmienić nazwę woluminu na coś takiego jak „kontener_node_modules”, aby to wyjaśnić. :)
Fabian
1
Uznałem również, że jest to najbardziej eleganckie rozwiązanie, które z łatwością pozwala odwoływać się do tej samej objętości modułów węzłów na kilku etapach kompilacji. Świetny artykuł Lekcje z budowania aplikacji węzłów w Docker również stosuje to samo podejście.
kf06925
1
@ kf06925 człowiek, który naprawdę mnie uratowałeś, spędziłem godziny próbując rozwiązać ten problem i dzięki artykułowi mogłem !! Kupiłbym ci piwo, gdybym mógł bardzo podziękować
helado
21

Ostatnio miałem podobny problem. Możesz zainstalować node_modulesgdzie indziej i ustawić NODE_PATHzmienną środowiskową.

W poniższym przykładzie zainstalowałem node_modulesw/install

pracownik / plik Docker

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/

docker-compose.yml

redis:
    image: redis
worker:
    build: ./worker
    command: npm start
    ports:
        - "9730:9730"
    volumes:
        - worker/:/worker/
    links:
        - redis
ericstolten
źródło
6
Najlepiej ocenione rozwiązanie @FrederikNS jest przydatne, ponieważ rozwiązałem inny problem z lokalnym woluminem, zastępując kontener na node_modulespodstawie tego artykułu . Ale doprowadziło mnie to do tego problemu . To rozwiązanie polegające na utworzeniu osobnego katalogu do skopiowania package.json, uruchomienia npm installtam, a następnie określeniu NODE_PATHzmiennej środowiskowej w docker-compose.ymlcelu wskazania node_modulesfolderu tego katalogu działa i wydaje się właściwe.
cwnewhouse
Włączenie ENV NODE_PATH = / install / node_modules w Dockerfile było dla mnie rozwiązaniem po wielu godzinach próbowania różnych podejść. Dziękuję Panu.
Benny Meade
Co jeśli uruchomisz npm installna hoście? Wygląda na node_modulesto, że pojawi się na hoście i zostanie odzwierciedlony w kontenerze, przejmując pierwszeństwo NODE_PATH. Tak więc kontener użyje modułów node_modules z hosta.
vitalets
19

Istnieje eleganckie rozwiązanie:

Po prostu zamontuj nie cały katalog, ale tylko katalog aplikacji. W ten sposób nie będziesz mieć problemównpm_modules .

Przykład:

  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
źródło
Znakomity. Nie mogę zrozumieć, dlaczego nie jest to akceptowana odpowiedź.
Jivan
2
To dobre i szybkie rozwiązanie, ale wymaga przebudowy i przycinania po instalacji nowej zależności.
Kunok
Teraz robię to inaczej, powinien istnieć wolumin specjalnie dla modułów node_modules i wolumin montowany z hosta. Wtedy nie ma żadnych problemów
Holms
14

AKTUALIZACJA: Skorzystaj z rozwiązania dostarczonego przez @FrederikNS.

Napotkałem ten sam problem. Kiedy folder/worker zostanie zamontowany w kontenerze - cała jego zawartość zostanie zsynchronizowana (więc folder node_modules zniknie, jeśli nie masz go lokalnie).

Z powodu niekompatybilnych pakietów npm opartych na systemie operacyjnym nie mogłem po prostu zainstalować modułów lokalnie - a następnie uruchomić kontener, więc ..

Moim rozwiązaniem tego było owinięcie źródła w srcfolderze, a następnie połączenie node_modulesz tym folderem przy użyciu tego pliku index.js . Tak, index.jsplik jest teraz punktem wyjścia mojej aplikacji.

Po uruchomieniu kontenera podłączyłem /app/srcfolder do mojego lokalnegosrc .

Tak więc folder kontenera wygląda mniej więcej tak:

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

Jest brzydka , ale działa ..

DŻEM
źródło
3
och, drogi panie ... nie mogę uwierzyć, że też się z tym utknąłem!
Lucas Pottersky
10

Ze względu na sposób, w jaki Node.js ładuje moduły , node_modulesmoże znajdować się w dowolnym miejscu ścieżki do kodu źródłowego. Na przykład umieść swoje źródło w /worker/srcswoim , a więc package.jsonw /worker, /worker/node_modulesgdzie są zainstalowane.

Justin Stayton
źródło
7

Instalowanie node_modules w kontenerze innym niż folder projektu i ustawienie NODE_PATH na folder node_modules pomaga mi (musisz odbudować kontener).

Używam komponowania dokera. Struktura mojego projektu:

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

docker-compose.yml:

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

Plik Docker w folderze 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
źródło
1
To najlepsze rozwiązanie, jakie znalazłem. NODE_PATHbył dla mnie kluczem.
cdignam
Myślę, że ma to sens, ale ustawienie NODE_PATH podczas uruchamiania obrazu CMD npm start nie używa podanej NODE_PATH.
Acton
6

Istnieje również proste rozwiązanie bez mapowania node_modulekatalogu na inny wolumin. Ma zamiar przenieść instalowanie pakietów npm do ostatecznego polecenia CMD.

Wada tego podejścia:

  • uruchamiaj za npm installkażdym razem, gdy uruchamiasz kontener (zmiana z npmna yarnmoże również nieco przyspieszyć ten proces).

pracownik / plik Docker

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

docker-compose.yml

redis:
    image: redis
worker:
    build: ./worker
    ports:
        - "9730:9730"
    volumes:
        - worker/:/worker/
    links:
        - redis
Egel
źródło
3

Są dwa oddzielne wymagania, które widzę dla środowisk deweloperskich węzłów ... podłącz kod źródłowy do kontenera i podłącz moduły node_z kontenera (dla twojego IDE). Aby wykonać pierwszy, wykonujesz zwykły montaż, ale nie wszystko ... tylko to, czego potrzebujesz

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

(powód, aby tego nie robić - /worker/node_modules jest to, że docker-compose zachowa ten wolumin między uruchomieniami, co oznacza, że ​​możesz odbiegać od tego, co jest faktycznie na obrazie (pokonując cel nie tylko wiązania montażu z hosta)).

Drugi jest w rzeczywistości trudniejszy. Moje rozwiązanie jest trochę hackerskie, ale działa. Mam skrypt do zainstalowania folderu node_modules na moim komputerze-hoście i muszę tylko pamiętać, aby wywoływać go za każdym razem, gdy aktualizuję pakiet.json (lub dodaj go do make make, który uruchamia kompilację dokerów lokalnie).

install_node_modules:
    docker build -t building .
    docker run -v `pwd`/node_modules:/app/node_modules building npm install
Paul Becotte
źródło
2

Moim zdaniem nie powinniśmy RUN npm installznajdować się w Dockerfile. Zamiast tego możemy uruchomić kontener za pomocą bash, aby zainstalować zależności przed uruchomieniem formalnej usługi węzła

docker run -it -v ./app:/usr/src/app  your_node_image_name  /bin/bash
root@247543a930d6:/usr/src/app# npm install
salamandra
źródło
Zgadzam się z tobą w tej sprawie. Woluminów należy używać, gdy chcesz udostępnić dane między kontenerem a hostem. Kiedy zdecydujesz się node_modulesbyć wytrwały nawet po wyjęciu pojemnika, powinieneś również wiedzieć, kiedy lub kiedy nie robić tego npm installręcznie. OP sugeruje zrobienie tego na każdej kompilacji obrazu . Możesz to zrobić, ale nie musisz do tego używać woluminu. W każdej wersji moduły będą zawsze aktualne.
phil294
@Blauhirn pomocne jest zamontowanie lokalnego woluminu hosta w kontenerze podczas wykonywania np. Gulp watch (lub analogicznych poleceń) - chcesz, aby moduły node_modu były utrzymywane, jednocześnie pozwalając na zmiany w innych źródłach (js, css itp.). npm nalega na użycie lokalnego łyka, więc musi się utrzymywać (lub być instalowany przy użyciu innych metod podczas uruchamiania)
tbm
2

Możesz spróbować czegoś takiego w swoim pliku Docker:

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

Następnie powinieneś użyć woluminu w następujący sposób:

volumes:
  - worker/:/worker:rw

Skrypt startowy powinien być częścią repozytorium pracownika i wygląda następująco:

#!/bin/sh
npm install
npm start

Moduły node_modu są częścią woluminu roboczego i są synchronizowane, a skrypty npm są wykonywane, gdy wszystko działa.

Parav01d
źródło
Spowoduje to duże obciążenie związane z uruchomieniem kontenera.
tbm
2
Ale tylko za pierwszym razem, ponieważ moduły node_modu zostaną zachowane na komputerze lokalnym.
Parav01d
Lub do czasu odbudowania obrazu lub usunięcia woluminu :). To powiedziawszy, sam nie znalazłem lepszego rozwiązania.
tbm
0

Możesz również porzucić swój plik Docker, ze względu na jego prostotę, wystarczy użyć podstawowego obrazu i podać polecenie w pliku tworzenia:

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

Jest to szczególnie przydatne dla mnie, ponieważ potrzebuję tylko środowiska obrazu, ale działam na moich plikach poza kontenerem i myślę, że to też chcesz zrobić.

itmuckel
źródło