Docker-compose: node_modules non présent dans un volume après la réussite de l'installation de npm

184

J'ai une application avec les services suivants:

  • web/ - contient et exécute un serveur Web flask python 3 sur le port 5000. Utilise sqlite3.
  • worker/- a un index.jsfichier qui est un ouvrier pour une file d'attente. le serveur Web interagit avec cette file d'attente à l'aide d'une API json sur le port 9730. Le travailleur utilise redis pour le stockage. Le travailleur stocke également les données localement dans le dossierworker/images/

Or cette question ne concerne que le 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

Quand je cours docker-compose build, tout fonctionne comme prévu et tous les modules npm sont installés /worker/node_modulescomme je m'y attendais.

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>

Mais quand je le fais docker-compose up, je vois cette erreur:

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)

Il s'avère qu'aucun des modules n'est présent dans /worker/node_modules(sur l'hôte ou dans le conteneur).

Si sur l'hôte, je npm install, alors tout fonctionne très bien. Mais je ne veux pas faire ça. Je veux que le conteneur gère les dépendances.

Qu'est-ce qui ne va pas ici?

(Inutile de dire que tous les packages sont inclus package.json.)

KGo
la source
Avez-vous fini par trouver une solution?
Justin Stayton
Je pense que vous devriez utiliser l'instruction ONBUILD ... Comme ceci: github.com/nodejs/docker-node/blob/master/0.12/onbuild/…
Lucas Pottersky
1
Comment feriez-vous le développement sur l'hôte lorsque l'EDI ne connaît pas les dépendances node_module?
André
2
Essayez de supprimer le volumes: - worker/:/worker/bloc du docker-compose.ymlfichier. Cette ligne écrase le dossier que vous créez avec la commande COPY.
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.- Comment avez-vous vérifié cela?
Vallie

Réponses:

272

Cela se produit parce que vous avez ajouté votre workerrépertoire en tant que volume à votre docker-compose.yml, car le volume n'est pas monté pendant la construction.

Lorsque docker crée l'image, le node_modulesrépertoire est créé dans le workerrépertoire et toutes les dépendances y sont installées. Ensuite, lors de l'exécution, le workerrépertoire de l'extérieur du docker est monté dans l'instance de docker (qui n'a pas installé node_modules), masquant le fichier que node_modulesvous venez d'installer. Vous pouvez le vérifier en supprimant le volume monté de votre docker-compose.yml.

Une solution de contournement consiste à utiliser un volume de données pour stocker tous les node_modules, car les volumes de données sont copiés dans les données de l'image Docker générée avant le workermontage du répertoire. Cela peut être fait de la manière docker-compose.ymlsuivante:

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

Je ne suis pas tout à fait certain que cela pose des problèmes pour la portabilité de l'image, mais comme il semble que vous utilisez principalement docker pour fournir un environnement d'exécution, cela ne devrait pas être un problème.

Si vous souhaitez en savoir plus sur les volumes, il y a un joli guide de l'utilisateur disponible ici: https://docs.docker.com/userguide/dockervolumes/

EDIT: Docker a depuis changé sa syntaxe pour exiger un ./début pour le montage dans les fichiers par rapport au fichier docker-compose.yml.

FrederikNS
la source
7
excellente réponse, explication la moins intrusive et excellente!
kkemple
41
J'ai essayé cette méthode et j'ai frappé le mur lorsque les dépendances ont changé. J'ai reconstruit l'image, commencé un nouveau conteneur et le volume est /worker/node_modulesresté le même qu'avant (avec les anciennes dépendances). Existe-t-il une astuce pour utiliser un nouveau volume lors de la reconstruction de l'image?
Ondrej Slinták
11
Il semble que docker compose ne supprime pas les volumes si d'autres conteneurs les utilisent (même s'ils sont morts). Donc, s'il y a des conteneurs morts du même type (pour une raison quelconque), le scénario que j'ai décrit dans le commentaire précédent suit. D'après ce que j'ai essayé, l'utilisation docker-compose rmsemble résoudre ce problème, mais je pense qu'il doit y avoir une solution meilleure et plus simple.
Ondrej Slinták
15
Y a-t-il une solution en 2018 sans avoir à rebuild --no-cachechaque fois que les déps changent?
Eelke
7
vous pouvez maintenant utiliser `--renew-anon-volumes` qui recréera des volumes anonymes au lieu des données des conteneurs précédents.
Mohammed Essehemy
37

Le node_modulesdossier est écrasé par le volume et n'est plus accessible dans le conteneur. J'utilise la stratégie de chargement du module natif pour retirer le dossier du volume:

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

Le node_modulesrépertoire n'est pas accessible depuis l'extérieur du conteneur car il est inclus dans l'image.

jsan
la source
1
y a-t-il des inconvénients à cette approche? Semble bien fonctionner pour moi.
Bret Fisher
1
node_modulesn'est pas accessible de l'extérieur du conteneur mais pas vraiment un inconvénient;)
jsan
et chaque fois que vous changez package.json, vous devez reconstruire tout le conteneur avec --no-cache, non?
Luke
Vous devez reconstruire l'image lorsque vous changez package.json, oui, mais --no-cache n'est pas nécessaire. Si vous exécutez, docker-compose run app npm installvous allez créer un node_modules dans le répertoire courant et vous n'avez plus besoin de reconstruire l'image.
jsan
9
L'inconvénient est: plus d'autocomplétion IDE, pas d'aide, pas de bonne expérience de développement. Tout doit également être installé sur l'hôte maintenant, mais la raison pour laquelle vous utilisez docker ici n'est-elle pas que l'hôte de développement n'a besoin de rien pour pouvoir travailler avec un projet?
Michael B.
31

La solution fournie par @FrederikNS fonctionne, mais je préfère nommer explicitement mon volume node_modules.

Mon project/docker-compose.ymlfichier (docker-compose version 1.6+):

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

ma structure de fichiers est:

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

Il crée un volume nommé project_node_moduleset le réutilise à chaque fois que j'utilise mon application.

Mon docker volume lsressemble à ceci:

DRIVER              VOLUME NAME
local               project1_mysql
local               project1_node_modules
local               project2_postgresql
local               project2_node_modules
Guillaume Vincent
la source
3
pendant que cela «fonctionne», vous contournez un vrai concept dans docker: toutes les dépendances doivent être intégrées pour une portabilité maximale. vous ne pouvez pas déplacer cette image sans exécuter d'autres commandes qui soufflent un peu
Javier Buzzi
4
J'ai essayé de résoudre le même problème pendant des heures et j'ai trouvé la même solution moi-même. Votre réponse devrait être la mieux notée, mais je suppose que ppl n'obtient pas votre réponse parce que vous avez nommé votre volume "node_modules" et que tous les lecteurs manquent le fait que cela crée un nouveau volume. En lisant votre code, j'ai pensé que vous venez de «remonter» le dossier de la locale node_modules et que vous avez rejeté cette idée. Peut-être que vous devriez modifier le nom du volume en quelque chose comme "container_node_modules" pour que cela soit clair. :)
Fabian
1
J'ai également trouvé que c'était la solution la plus élégante, qui permet facilement de faire référence au même volume pour les modules de nœuds à travers plusieurs étapes de construction.L'excellent article Lessons from Building Node Apps in Docker utilise également la même approche.
kf06925 le
1
@ kf06925 mec tu m'as vraiment sauvé, j'ai passé des heures à essayer de résoudre ça et grâce à l'article j'ai pu !! Je t'achèterais une bière si je pouvais te remercier
helado
21

J'ai récemment eu un problème similaire. Vous pouvez installer node_modulesailleurs et définir la NODE_PATHvariable d'environnement.

Dans l'exemple ci-dessous, j'ai installé node_modulesdans/install

worker / 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
la source
6
La solution la plus votée par @FrederikNS est utile, c'est comment j'ai résolu un problème différent de mon volume local écrasant le conteneur node_modulesbasé sur cet article . Mais cela m'a amené à faire l'expérience de ce problème . Cette solution ici de création d'un répertoire séparé pour copier votre package.json, exécutez- npm instally, puis spécifiez la NODE_PATHvariable d'environnement docker-compose.ymlpour pointer vers le node_modulesdossier de ce répertoire fonctionne et semble juste.
cwnewhouse
Inclure ENV NODE_PATH = / install / node_modules dans Dockerfile était finalement la solution pour moi après des heures à essayer différentes approches. Merci Monsieur.
Benny Meade
Et si vous exécutez npm installsur l'hôte? Il semble node_modulesapparaître sur l'hôte et être reflété dans le conteneur, en prenant la priorité sur NODE_PATH. Ainsi, le conteneur utilisera node_modules de l'hôte.
vitalets
19

Il existe une solution élégante:

Ne montez pas tout le répertoire, mais uniquement le répertoire de l'application. De cette façon, vous n'aurez aucun problème avec npm_modules.

Exemple:

  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
la source
Brillant. Je ne comprends pas pourquoi ce n'est pas la réponse acceptée.
Jivan
2
C'est une bonne solution rapide, mais elle nécessite des reconstructions et des élagages après l'installation d'une nouvelle dépendance.
Kunok
Je le fais différemment maintenant, il devrait y avoir un volume spécifiquement pour node_modules et un volume que vous montez à partir de l'hôte. Ensuite, il n'y a aucun problème
holms
14

MISE À JOUR: utilisez la solution fournie par @FrederikNS.

J'ai rencontré le même problème. Lorsque le dossier /workerest monté sur le conteneur - tout son contenu sera synchronisé (ainsi le dossier node_modules disparaîtra si vous ne l'avez pas localement.)

En raison de packages npm incompatibles basés sur le système d'exploitation, je ne pouvais pas simplement installer les modules localement - puis lancer le conteneur, donc ..

Ma solution à cela consistait à envelopper la source dans un srcdossier, puis node_modulesà créer un lien dans ce dossier, en utilisant ce fichier index.js . Donc, le index.jsfichier est maintenant le point de départ de mon application.

Lorsque j'exécute le conteneur, j'ai monté le /app/srcdossier dans mon srcdossier local .

Le dossier du conteneur ressemble donc à ceci:

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

C'est moche , mais ça marche.

CONFITURE
la source
3
oh, cher seigneur ... je ne peux pas croire que je suis aussi coincé avec ça!
Lucas Pottersky
10

En raison de la façon dont Node.js charge les modules , il node_modulespeut se trouver n'importe où dans le chemin de votre code source. Par exemple, placez votre source à /worker/srcet votre package.jsondans /worker, c'est /worker/node_moduleslà où ils sont installés.

Justin Stayton
la source
7

L'installation de node_modules dans un conteneur à un autre que le dossier du projet et la définition de NODE_PATH dans votre dossier node_modules m'aide (vous devez reconstruire le conteneur).

J'utilise docker-compose. Ma structure de fichiers de projet:

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

Dockerfile dans le dossier 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
sergeysynergie
la source
1
C'est la meilleure solution que j'ai trouvée. NODE_PATHétait la clé pour moi.
cdignam
Je pense que cela a du sens, mais en définissant NODE_PATH, lors de l'exécution de l'image, CMD npm start n'utilise pas le NODE_PATH spécifié.
Acton le
6

Il existe également une solution simple sans mappage de node_modulerépertoire dans un autre volume. Il est sur le point de déplacer l'installation des packages npm dans la commande CMD finale.

Inconvénient de cette approche:

  • exécutez npm installchaque fois que vous exécutez le conteneur (le passage de npmà yarnpeut également accélérer un peu ce processus).

worker / 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
la source
3

Il y a deux exigences distinctes que je vois pour les environnements de développement de nœuds ... montez votre code source DANS le conteneur et montez les node_modules À PARTIR du conteneur (pour votre IDE). Pour accomplir le premier, vous faites le montage habituel, mais pas tout ... juste ce dont vous avez besoin

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

(la raison de ne pas faire - /worker/node_modules est que docker-compose conservera ce volume entre les exécutions, ce qui signifie que vous pouvez diverger de ce qui est réellement dans l'image (ce qui va à l'encontre de l'objectif de ne pas simplement lier le montage de votre hôte)).

Le second est en fait plus difficile. Ma solution est un peu hackish, mais ça marche. J'ai un script pour installer le dossier node_modules sur ma machine hôte, et je dois juste me rappeler de l'appeler chaque fois que je mets à jour package.json (ou l'ajouter à la cible make qui exécute docker-compose build localement).

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

À mon avis, nous ne devrions pas RUN npm installdans le Dockerfile. Au lieu de cela, nous pouvons démarrer un conteneur en utilisant bash pour installer les dépendances avant d'exécuter le service de nœud formel

docker run -it -v ./app:/usr/src/app  your_node_image_name  /bin/bash
root@247543a930d6:/usr/src/app# npm install
salamandre
la source
Je suis en fait d'accord avec vous sur celui-ci. Les volumes sont destinés à être utilisés lorsque vous souhaitez partager les données entre le conteneur et l'hôte. Lorsque vous décidez de node_modulesrester persistant même après la suppression du conteneur, vous devez également savoir quand ou quand ne pas le faire npm installmanuellement. OP suggère de le faire sur chaque construction d'image . Vous pouvez le faire, mais vous n'avez pas besoin d'utiliser également un volume pour cela. Sur chaque build, les modules seront à jour de toute façon.
phil294
@Blauhirn, il est utile de monter un volume hôte local dans le conteneur lorsque vous faites par exemple gulp watch (ou des commandes analogues) - vous voulez que les node_modules persistent tout en autorisant les changements vers d'autres sources (js, css, etc.). npm insiste pour utiliser un gulp local, il doit donc persister (ou être installé via d'autres méthodes au démarrage)
tbm
2

Vous pouvez essayer quelque chose comme ça dans votre Dockerfile:

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

Ensuite, vous devez utiliser le volume comme ceci:

volumes:
  - worker/:/worker:rw

Le startscript doit faire partie de votre référentiel de travail et ressemble à ceci:

#!/bin/sh
npm install
npm start

Ainsi, les node_modules font partie de votre volume de travail et sont synchronisés et les scripts npm sont exécutés lorsque tout est en place.

Parav01d
la source
Cela ajoutera une surcharge importante au démarrage du conteneur.
confirmer
2
Mais seulement la première fois car les node_modules seront conservés sur la machine locale.
Parav01d
Ou jusqu'à ce que l'image soit reconstruite ou que le volume soit supprimé :). Cela dit, je n'ai pas trouvé de meilleure solution moi-même.
tbm
0

Vous pouvez également abandonner votre Dockerfile, en raison de sa simplicité, utilisez simplement une image de base et spécifiez la commande dans votre fichier de composition:

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

C'est particulièrement utile pour moi, car j'ai juste besoin de l'environnement de l'image, mais j'opère sur mes fichiers en dehors du conteneur et je pense que c'est ce que tu veux faire aussi.

itmuckel
la source