Docker-compose: node_modules sind nach erfolgreicher npm-Installation nicht in einem Volume vorhanden

182

Ich habe eine App mit folgenden Diensten:

  • web/ - hält einen Python 3-Flask-Webserver auf Port 5000 und führt ihn aus. Verwendet sqlite3.
  • worker/- hat eine index.jsDatei, die ein Arbeiter für eine Warteschlange ist. Der Webserver interagiert mit dieser Warteschlange über eine JSON-API über Port 9730. Der Worker verwendet Redis zur Speicherung. Der Worker speichert Daten auch lokal im Ordnerworker/images/

Jetzt betrifft diese Frage nur die 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

Wenn ich laufe docker-compose build, funktioniert alles wie erwartet und alle npm-Module werden wie erwartet installiert /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>

Aber wenn ich das tue docker-compose up, sehe ich diesen Fehler:

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)

Es stellt sich heraus, dass keines der Module vorhanden ist /worker/node_modules(auf dem Host oder im Container).

Wenn auf dem Host, ich npm install, dann funktioniert alles gut. Aber das will ich nicht. Ich möchte, dass der Container Abhängigkeiten behandelt.

Was läuft hier falsch?

(Unnötig zu erwähnen, dass alle Pakete enthalten sind package.json.)

KGo
quelle
Haben Sie am Ende eine Lösung gefunden?
Justin Stayton
Ich denke, Sie sollten die ONBUILD-Anweisung verwenden ... So: github.com/nodejs/docker-node/blob/master/0.12/onbuild/…
Lucas Pottersky
1
Wie würden Sie die Entwicklung auf dem Host durchführen, wenn die IDE die Abhängigkeiten von node_module nicht kennt?
André
2
Versuchen Sie, den volumes: - worker/:/worker/Block aus der docker-compose.ymlDatei zu entfernen . Diese Zeile überschreibt den Ordner, den Sie mit dem Befehl COPY erstellen.
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.- Wie hast du das überprüft?
Vallie

Antworten:

271

Dies liegt daran, dass Sie Ihr workerVerzeichnis als Volume zu Ihrem hinzugefügt haben docker-compose.yml, da das Volume während des Builds nicht bereitgestellt wird.

Wenn Docker das Image erstellt, wird das node_modulesVerzeichnis innerhalb des workerVerzeichnisses erstellt und alle Abhängigkeiten werden dort installiert. Zur Laufzeit wird das workerVerzeichnis von außerhalb des Dockers in die Docker-Instanz eingebunden (auf der das Docker nicht installiert ist node_modules), wodurch das node_modulesgerade installierte Verzeichnis ausgeblendet wird . Sie können dies überprüfen, indem Sie das bereitgestellte Volume von Ihrem entfernen docker-compose.yml.

Eine Problemumgehung besteht darin, ein Datenvolume zum Speichern aller Daten zu verwenden node_modules, da Datenvolumes die Daten aus dem erstellten Docker-Image kopieren, bevor das workerVerzeichnis bereitgestellt wird. Dies kann folgendermaßen geschehen docker-compose.yml:

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

Ich bin nicht ganz sicher, ob dies Probleme für die Portabilität des Images mit sich bringt, aber da Sie anscheinend hauptsächlich Docker verwenden, um eine Laufzeitumgebung bereitzustellen, sollte dies kein Problem sein.

Wenn Sie mehr über Volumes erfahren möchten, finden Sie hier eine nette Bedienungsanleitung: https://docs.docker.com/userguide/dockervolumes/

BEARBEITEN: Docker hat seitdem seine Syntax dahingehend geändert, dass ein Leading ./für das Mounten in Dateien relativ zur Datei docker-compose.yml erforderlich ist.

FrederikNS
quelle
7
tolle Antwort, am wenigsten aufdringlich und tolle Erklärung!
kkemple
40
Ich habe diese Methode ausprobiert und bin an die Wand gestoßen, als sich die Abhängigkeiten geändert haben. Ich habe das Image neu erstellt, einen neuen Container gestartet und das Volume ist /worker/node_modulesunverändert geblieben (mit alten Abhängigkeiten). Gibt es einen Trick, wie man beim Wiederherstellen des Images ein neues Volume verwendet?
Ondrej Slinták
10
Es scheint, dass Docker Compose keine Volumes entfernt, wenn andere Container sie verwenden (auch wenn sie tot sind). Wenn es also einige tote Container desselben Typs gibt (aus welchem ​​Grund auch immer), folgt das Szenario, das ich im vorherigen Kommentar beschrieben habe. Nach allem, was ich versucht habe, docker-compose rmscheint die Verwendung dieses Problems zu beheben, aber ich glaube, es muss eine bessere und einfachere Lösung geben.
Ondrej Slinták
14
Gibt es 2018 eine Lösung, ohne dass sich die rebuild --no-cacheDeps jedes Mal ändern müssen?
Eelke
7
Sie können jetzt "--renew-anon-volume" verwenden , um anonyme Volumes anstelle von Daten aus den vorherigen Containern neu zu erstellen.
Mohammed Essehemy
37

Der node_modulesOrdner wird vom Volume überschrieben und ist im Container nicht mehr zugänglich. Ich verwende die native Modul-Ladestrategie , um den Ordner aus dem Volume zu entfernen:

/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/

Auf das node_modulesVerzeichnis kann von außerhalb des Containers nicht zugegriffen werden, da es im Bild enthalten ist.

jsan
quelle
1
Gibt es Nachteile bei diesem Ansatz? Scheint gut für mich zu funktionieren.
Bret Fisher
1
node_modulesist nicht von außerhalb des Containers zugänglich, aber nicht wirklich ein Nachteil;)
jsan
und jedes Mal, wenn Sie package.json ändern, müssen Sie den gesamten Container mit --no-cache neu erstellen, oder?
Luke
Sie müssen das Image neu erstellen, wenn Sie package.json ändern, ja, aber --no-cache ist nicht erforderlich. Wenn Sie ausführen docker-compose run app npm install, erstellen Sie ein Knotenmodul im aktuellen Verzeichnis und müssen das Image nicht mehr neu erstellen.
jsan
8
Der Nachteil ist: keine IDE-Autovervollständigung mehr, keine Hilfe, keine gute Entwicklererfahrung. Alles muss jetzt auch auf dem Host installiert werden, aber ist der Grund für die Verwendung von Docker hier nicht, dass der Dev-Host nichts benötigt, um mit einem Projekt arbeiten zu können?
Michael B.
31

Die von @FrederikNS bereitgestellte Lösung funktioniert, aber ich bevorzuge es, mein node_modules-Volume explizit zu benennen.

Meine project/docker-compose.ymlDatei (Docker-Compose Version 1.6+):

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

Meine Dateistruktur ist:

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

Es erstellt ein Volume mit dem Namen project_node_modulesund verwendet es jedes Mal wieder, wenn ich meine Anwendung starte.

Mein docker volume lssieht so aus:

DRIVER              VOLUME NAME
local               project1_mysql
local               project1_node_modules
local               project2_postgresql
local               project2_node_modules
Guillaume Vincent
quelle
3
Während dies „funktioniert“, umgehen Sie im Docker ein echtes Konzept: Alle Abhängigkeiten sollten für maximale Portabilität eingebrannt werden. Sie können dieses Bild nicht verschieben, ohne andere Befehle auszuführen, die irgendwie blasen
Javier Buzzi
4
versuchte stundenlang das gleiche Problem zu lösen und fand selbst die gleiche Lösung. Ihre Antwort sollte die höchste Bewertung sein, aber ich denke, ppl erhält Ihre Antwort nicht, weil Sie Ihr Volume "node_modules" genannt haben und allen Lesern die Tatsache fehlt, dass dadurch ein neues Volume erstellt wird. Beim Lesen Ihres Codes dachte ich, dass Sie den Ordner "node_modules" des Gebietsschemas einfach "neu mounten" und diese Idee verwerfen. Vielleicht sollten Sie den Datenträgernamen in "container_node_modules" ändern, um dies zu verdeutlichen. :)
Fabian
1
Ich fand auch, dass dies die eleganteste Lösung ist, mit der sich Knotenmodule über mehrere Erstellungsphasen hinweg leicht auf dasselbe Volume beziehen lassen. Der großartige Artikel Lektionen aus dem Erstellen von Knoten-Apps in Docker verwendet ebenfalls denselben Ansatz.
kf06925
1
@ kf06925 Mann, du hast mich wirklich gerettet, ich habe Stunden damit verbracht, dies zu lösen und dank des Artikels konnte ich !! Ich würde dir ein Bier kaufen, wenn ich mich so sehr
bedanken
21

Ich hatte kürzlich ein ähnliches Problem. Sie können an einer node_modulesanderen Stelle installieren und die NODE_PATHUmgebungsvariable festlegen .

Im Beispiel unten habe ich installiert node_modulesin/install

Arbeiter / 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/

docker-compose.yml

redis:
    image: redis
worker:
    build: ./worker
    command: npm start
    ports:
        - "9730:9730"
    volumes:
        - worker/:/worker/
    links:
        - redis
ericstolten
quelle
6
Die von @FrederikNS am besten gewählte Lösung war nützlich. Ich habe ein anderes Problem gelöst, bei dem mein lokales Volume die Container node_modulesbasierend auf diesem Artikel überschrieb . Aber es hat mich dazu gebracht, dieses Problem zu erleben . Diese Lösung besteht darin, ein separates Verzeichnis zu erstellen package.json, in das Sie npm installes kopieren , dort ausführen und dann die NODE_PATHUmgebungsvariable angeben können docker-compose.yml, um auf den node_modulesOrdner dieses Verzeichnisses zu verweisen .
Cwnewhouse
Das Einfügen von ENV NODE_PATH = / install / node_modules in Dockerfile war endlich die Lösung für mich, nachdem ich stundenlang verschiedene Ansätze ausprobiert hatte. Danke mein Herr.
Benny Meade
Was ist, wenn Sie npm installauf dem Host laufen ? Es scheint node_modulesauf dem Host zu erscheinen und wird im Container wiedergegeben, wobei Vorrang vor dem Container besteht NODE_PATH. Der Container verwendet also node_modules vom Host.
Vitalets
18

Es gibt eine elegante Lösung:

Mounten Sie einfach nicht das gesamte Verzeichnis, sondern nur das App-Verzeichnis. Auf diese Weise haben Sie keine Probleme npm_modules.

Beispiel:

  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
quelle
Brillant. Ich kann nicht verstehen, warum es nicht die akzeptierte Antwort ist.
Jivan
2
Dies ist eine gute und schnelle Lösung, die jedoch nach einer neuen Abhängigkeitsinstallation Neuerstellungen und Bereinigungen erfordert.
Kunok
Ich mache es jetzt anders, es sollte ein Volume speziell für node_modules und ein Volume geben, das Sie vom Host mounten. Dann gibt es überhaupt keine Probleme
Holms
13

UPDATE: Verwenden Sie die Lösung von @FrederikNS zur Verfügung gestellt.

Ich bin auf das gleiche Problem gestoßen. Wenn der Ordner /workerin den Container eingebunden wird, wird der gesamte Inhalt synchronisiert (sodass der Ordner node_modules verschwindet, wenn Sie ihn nicht lokal haben.)

Aufgrund inkompatibler npm-Pakete, die auf dem Betriebssystem basieren, konnte ich die Module nicht nur lokal installieren, sondern auch den Container starten.

Meine Lösung bestand darin, die Quelle in einen srcOrdner zu packen und dann node_modulesmit dieser Datei index.js in diesen Ordner zu verlinken . Die index.jsDatei ist nun der Ausgangspunkt meiner Anwendung.

Wenn ich den Container ausführe, habe ich den /app/srcOrdner in meinen lokalen srcOrdner eingebunden.

Der Containerordner sieht also ungefähr so ​​aus:

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

Es ist hässlich , aber es funktioniert ..

MARMELADE
quelle
3
Oh, lieber Herr ... ich kann nicht glauben, dass ich auch daran festhalte!
Lucas Pottersky
10

Aufgrund der Art und Weise, wie Node.js Module lädt , node_moduleskann es sich an einer beliebigen Stelle im Pfad zu Ihrem Quellcode befinden. Legen Sie beispielsweise Ihre Quelle an /worker/srcund Ihre package.jsonin /worker, also /worker/node_modulesdort, wo sie installiert sind.

Justin Stayton
quelle
7

Das Installieren von node_modules im Container in einem anderen als dem Projektordner und das Festlegen von NODE_PATH in Ihrem node_modules-Ordner hilft mir (Sie müssen den Container neu erstellen).

Ich benutze Docker-Compose. Meine Projektdateistruktur:

-/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

Docker-Datei im Ordner 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
quelle
1
Dies ist die beste Lösung, die ich gefunden habe. NODE_PATHwar der Schlüssel für mich.
Cdignam
Ich denke, dies ist sinnvoll, aber durch Festlegen von NODE_PATH wird beim Ausführen des Bildes CMD npm start nicht das angegebene NODE_PATH verwendet.
Acton
6

Es gibt auch eine einfache Lösung, ohne das node_moduleVerzeichnis einem anderen Volume zuzuordnen. Es geht darum, die Installation von npm-Paketen in den endgültigen CMD-Befehl zu verschieben.

Nachteil dieses Ansatzes:

  • npm installJedes Mal ausführen, wenn Sie einen Container ausführen (ein Wechsel von npmzu yarnkann diesen Vorgang ebenfalls etwas beschleunigen).

Arbeiter / Dockerfile

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
quelle
3

Es gibt zwei separate Anforderungen, die ich für Knotenentwicklungsumgebungen sehe ... Mounten Sie Ihren Quellcode in den Container und mounten Sie die Node_Module aus dem Container (für Ihre IDE). Um das erste zu erreichen, machst du das übliche Reittier, aber nicht alles ... nur die Dinge, die du brauchst

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

(Der Grund dafür - /worker/node_modulesist, dass Docker-Compose dieses Volume zwischen den Läufen beibehält, was bedeutet, dass Sie von dem, was tatsächlich im Image enthalten ist, abweichen können (was den Zweck zunichte macht, nicht nur das Mounten von Ihrem Host zu binden)).

Der zweite ist eigentlich schwieriger. Meine Lösung ist ein bisschen hackisch, aber es funktioniert. Ich habe ein Skript zum Installieren des Ordners node_modules auf meinem Hostcomputer, und ich muss nur daran denken, es aufzurufen, wenn ich package.json aktualisiere (oder es dem make-Ziel hinzufüge, auf dem Docker-Compose Build lokal ausgeführt wird).

install_node_modules:
    docker build -t building .
    docker run -v `pwd`/node_modules:/app/node_modules building npm install
Paul Becotte
quelle
2

Meiner Meinung nach sollten wir nicht RUN npm installin die Dockerfile. Stattdessen können wir einen Container mit bash starten, um die Abhängigkeiten zu installieren, bevor der formale Knotendienst ausgeführt wird

docker run -it -v ./app:/usr/src/app  your_node_image_name  /bin/bash
root@247543a930d6:/usr/src/app# npm install
Salamander
quelle
In diesem Punkt stimme ich Ihnen tatsächlich zu. Volumes sollen verwendet werden, wenn Sie die Daten zwischen Container und Host teilen möchten. Wenn Sie sich dafür entscheiden, dass Sie node_modulesauch nach dem Entfernen des Containers dauerhaft bleiben, sollten Sie auch wissen, wann oder wann Sie dies nicht npm installmanuell tun müssen. OP schlägt vor, dies bei jedem Image-Build zu tun . Sie können das tun, aber Sie müssen dafür nicht auch ein Volume verwenden. Bei jedem Build sind die Module auf jeden Fall auf dem neuesten Stand.
Phil294
@Blauhirn Es ist hilfreich, ein lokales Host-Volume in den Container zu mounten, wenn Sie z. B. gulp watch (oder analoge Befehle) ausführen. Sie möchten, dass die node_modules bestehen bleiben und dennoch Änderungen an anderen Quellen (js, css usw.) zulassen. npm besteht darauf, einen lokalen Schluck zu verwenden, daher muss er bestehen bleiben (oder beim Start über andere Methoden installiert werden)
tbm
2

Sie können so etwas in Ihrer Docker-Datei ausprobieren:

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

Dann sollten Sie das Volume wie folgt verwenden:

volumes:
  - worker/:/worker:rw

Das Startskript sollte Teil Ihres Worker-Repositorys sein und sieht folgendermaßen aus:

#!/bin/sh
npm install
npm start

Die node_modules sind also Teil Ihres Worker-Volumes und werden synchronisiert und die npm-Skripte werden ausgeführt, wenn alles in Betrieb ist.

Parav01d
quelle
Dies erhöht den Start des Containers erheblich.
tbm
2
Aber nur das erste Mal, weil die node_modules auf dem lokalen Computer beibehalten werden.
Parav01d
Oder bis das Image neu erstellt oder das Volume entfernt wird :). Trotzdem habe ich selbst keine bessere Lösung gefunden.
tbm
0

Sie können Ihre Docker-Datei aufgrund ihrer Einfachheit auch über Bord werfen. Verwenden Sie einfach ein Basisbild und geben Sie den Befehl in Ihrer Erstellungsdatei an:

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

Dies ist besonders nützlich für mich, da ich nur die Umgebung des Bildes benötige, aber meine Dateien außerhalb des Containers bearbeite und ich denke, dass Sie dies auch tun möchten.

itmuckel
quelle