Docker-compose: node_modules non presente in un volume al termine dell'installazione di npm


184

Ho un'app con i seguenti servizi:

  • web/ - contiene ed esegue un server web di fiaschi Python 3 sulla porta 5000. Utilizza sqlite3.
  • worker/- ha un index.jsfile che è un lavoratore per una coda. il web server interagisce con questa coda usando un'API json su porta 9730. Il lavoratore utilizza redis per l'archiviazione. Il lavoratore archivia anche i dati localmente nella cartellaworker/images/

Ora questa domanda riguarda solo il 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

Quando corro docker-compose build, tutto funziona come previsto e tutti i moduli npm sono installati /worker/node_modulescome previsto.

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>

Ma quando lo faccio docker-compose up, vedo questo errore:

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)

Risulta che nessuno dei moduli è presente in /worker/node_modules(sull'host o nel contenitore).

Se sull'host, io npm install, allora tutto funziona bene. Ma non voglio farlo. Voglio che il contenitore gestisca le dipendenze.

Cosa non va qui?

(Inutile dire che tutti i pacchetti sono presenti package.json.)


Hai trovato una soluzione?
Justin Stayton,

Penso che dovresti usare l'istruzione ONBUILD ... In questo modo: github.com/nodejs/docker-node/blob/master/0.12/onbuild/…
Lucas Pottersky

1
Come faresti lo sviluppo sull'host quando l'IDE non conosce le dipendenze node_module?
André,

2
Prova a rimuovere il volumes: - worker/:/worker/blocco dal docker-compose.ymlfile. Questa riga sovrascrive la cartella creata con il comando COPIA.
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.- Come hai controllato?
Vallie il

Risposte:


272

Ciò accade perché hai aggiunto la tua workerdirectory come volume al tuo docker-compose.yml, poiché il volume non è montato durante la compilazione.

Quando la finestra mobile crea l'immagine, la node_modulesdirectory viene creata all'interno della workerdirectory e tutte le dipendenze vengono installate lì. Quindi in fase di esecuzione la workerdirectory dalla finestra mobile esterna viene montata nell'istanza della finestra mobile (che non ha installato node_modules), nascondendo il file node_modulesappena installato. Puoi verificarlo rimuovendo il volume montato dal tuo docker-compose.yml.

Una soluzione alternativa consiste nell'utilizzare un volume di dati per archiviare tutti node_modules, poiché i volumi di dati copiano i dati dall'immagine docker integrata prima che workervenga montata la directory. Questo può essere fatto in docker-compose.ymlquesto modo:

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

Non sono del tutto sicuro se ciò imponga problemi per la portabilità dell'immagine, ma poiché sembra che tu stia utilizzando principalmente la finestra mobile per fornire un ambiente di runtime, questo non dovrebbe essere un problema.

Se vuoi saperne di più sui volumi, c'è una bella guida per l'utente disponibile qui: https://docs.docker.com/userguide/dockervolumes/

EDIT: Docker da allora ha cambiato la sua sintassi per richiedere un lead ./per il montaggio in file relativi al file docker-compose.yml.


7
ottima risposta, spiegazione meno invadente e ottima!
kkemple,

41
Ho provato questo metodo e ho colpito il muro quando le dipendenze sono cambiate. Ho ricostruito l'immagine, avviato un nuovo contenitore e il volume è /worker/node_modulesrimasto lo stesso di prima (con vecchie dipendenze). C'è qualche trucco su come usare il nuovo volume dopo aver ricostruito l'immagine?
Ondrej Slinták,

11
Sembra che la finestra mobile componi non rimuova i volumi se li usano altri contenitori (anche se sono morti). Quindi, se ci sono alcuni contenitori morti dello stesso tipo (per qualsiasi motivo), segue lo scenario che ho descritto nel commento precedente. Da quello che ho provato, l'utilizzo docker-compose rmsembra risolvere questo problema, ma credo che ci debba essere una soluzione migliore e più semplice.
Ondrej Slinták,

15
Esiste una soluzione nel 2018 senza dover rebuild --no-cachecambiare ogni volta il deps?
Eelke

7
ora puoi usare `--renew-anon-volume` che ricrea i volumi anonimi invece dei dati dei contenitori precedenti.
Mohammed Essehemy,

37

La node_modulescartella viene sovrascritta dal volume e non è più accessibile nel contenitore. Sto usando la strategia di caricamento del modulo nativo per estrarre la cartella dal 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/

La node_modulesdirectory non è accessibile dall'esterno del contenitore perché è inclusa nell'immagine.


1
ci sono aspetti negativi di questo approccio? Sembra funzionare bene per me.
Bret Fisher,

1
node_modulesnon è accessibile dall'esterno del container, ma non è un
aspetto

e ogni volta che cambi package.json devi ricostruire l'intero contenitore con --no-cache giusto?
Luca

È necessario ricostruire l'immagine quando si cambia package.json, sì, ma --no-cache non è necessario. Se esegui docker-compose run app npm install, creerai un node_modules nella directory corrente e non dovrai più ricostruire l'immagine.
jsan

9
Il rovescio della medaglia è: niente più completamento automatico IDE, nessun aiuto, nessuna buona esperienza di sviluppo. Ora anche tutto deve essere installato sull'host, ma non è la ragione per usare la finestra mobile qui che l'host di sviluppo non ha bisogno di nulla per poter lavorare con un progetto?
Michael B.

31

La soluzione fornita da @FrederikNS funziona, ma preferisco dare un nome esplicito al mio volume node_modules.

Il mio project/docker-compose.ymlfile (docker-compose versione 1.6+):

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

la mia struttura dei file è:

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

Crea un volume denominato project_node_modulese lo riutilizza ogni volta che utilizzo l'applicazione.

Il mio docker volume lsassomiglia a questo:

DRIVER              VOLUME NAME
local               project1_mysql
local               project1_node_modules
local               project2_postgresql
local               project2_node_modules

3
mentre questo "funziona", stai aggirando un concetto reale nella finestra mobile: tutte le dipendenze dovrebbero essere cotte per la massima portabilità. non è possibile spostare questa immagine senza eseguire altri comandi che in qualche modo saltano
Javier Buzzi il

4
ho provato a risolvere lo stesso problema per ore e ho trovato la stessa soluzione da solo. La tua risposta dovrebbe essere la più votata, ma suppongo che ppl non ottenga la tua risposta perché hai chiamato il tuo volume "node_modules" e a tutti i lettori manca il fatto che ciò crei un nuovo volume. Durante la lettura del tuo codice ho pensato che hai "rimontato" la cartella locale node_modules e hai scartato quell'idea. Forse dovresti modificare il nome del volume in qualcosa come "container_node_modules" per chiarirlo. :)
Fabian,

1
Ho anche scoperto che questa è la soluzione più elegante, che consente facilmente di fare riferimento allo stesso volume per i moduli di nodo in diverse fasi di compilazione. Anche il grande articolo Lezioni da Building Node Apps in Docker utilizza lo stesso approccio.
kf06925,

1
@ kf06925 amico, mi hai davvero salvato, ho passato ore a cercare di risolvere questo problema e grazie all'articolo che ho potuto !! Vorrei offrirti una birra se potessi ringraziare tanto
helado

21

Di recente ho avuto un problema simile. È possibile installare node_modulesaltrove e impostare la NODE_PATHvariabile di ambiente.

Nell'esempio seguente ho installato node_modulesin/install

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

finestra mobile-compose.yml

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

6
La soluzione più votata da @FrederikNS è utile, è stato come ho risolto un diverso problema del mio volume locale sovrascrivendo il contenitore in node_modulesbase a questo articolo . Ma mi ha portato a provare questo problema . Questa soluzione qui di creare una directory separata in cui copiare package.json, eseguire npm installlì, quindi specificare la NODE_PATHvariabile di ambiente in modo docker-compose.ymlche punti alla node_modulescartella di quella directory funziona e sembra corretta.
Cwnewhouse,

Includere ENV NODE_PATH = / install / node_modules in Dockerfile è stata finalmente la soluzione per me dopo ore di tentativi di approcci diversi. Grazie Signore.
Benny Meade,

Cosa succede se si esegue npm installsu host? Sembra node_modulesche apparirà sull'host e si rifletterà sul contenitore, con priorità NODE_PATH. Quindi il contenitore utilizzerà node_modules dall'host.
vitalets

19

C'è una soluzione elegante:

Basta montare non l'intera directory, ma solo la directory dell'app. In questo modo non avrai problemi npm_modules.

Esempio:

  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

Brillante. Non riesco a capire perché non sia la risposta accettata.
Jivan,

2
Questa è una soluzione valida e veloce ma richiede ricostruzioni e potature dopo l'installazione di nuove dipendenze.
Kunok,

Lo faccio diversamente ora, dovrebbe esserci un volume specifico per node_modules e un volume che monti dall'host. Quindi non ci sono problemi
holms

14

AGGIORNAMENTO: utilizzare la soluzione fornita da @FrederikNS.

Ho riscontrato lo stesso problema. Quando la cartella /workerviene montata sul contenitore, tutto il suo contenuto verrà sincronizzato (quindi la cartella node_modules scomparirà se non è disponibile localmente).

A causa di pacchetti npm incompatibili basati sul sistema operativo, non potevo semplicemente installare i moduli localmente, quindi avviare il contenitore, quindi ...

La mia soluzione a questo, era di avvolgere l'origine in una srccartella, quindi collegarlo node_modulesa quella cartella, usando questo file index.js . Quindi, il index.jsfile è ora il punto di partenza della mia applicazione.

Quando eseguo il contenitore, ho montato la /app/srccartella nella mia srccartella locale .

Quindi la cartella del contenitore è simile alla seguente:

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

È brutto , ma funziona ..


3
oh, caro signore ... non riesco a credere di essere anche bloccato con quello!
Lucas Pottersky,


7

L'installazione di node_modules nel container su un diverso dalla cartella del progetto e l'impostazione di NODE_PATH sulla cartella node_modules mi aiuta (devi ricostruire il container).

Sto usando la finestra mobile-componi. La struttura del mio file di progetto:

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

finestra mobile-compose.yml:

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

File Docker nella cartella 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

1
Questa è la migliore soluzione che ho trovato. NODE_PATHera la chiave per me.
cdignam,

Penso che abbia senso, ma impostando NODE_PATH, quando si esegue l'immagine, CMD npm start non si utilizza il NODE_PATH specificato.
Acton,

6

C'è anche qualche soluzione semplice senza mappare la node_moduledirectory in un altro volume. Sta per spostare l'installazione dei pacchetti npm nel comando CMD finale.

Svantaggio di questo approccio:

  • eseguire npm installogni volta che si esegue il contenitore (il passaggio da npma yarnpotrebbe anche accelerare un po 'questo processo).

lavoratore / Dockerfile

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

finestra mobile-compose.yml

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

3

Esistono due requisiti separati che vedo per gli ambienti di sviluppo dei nodi ... montare il codice sorgente nel contenitore e montare node_modules DAL contenitore (per il proprio IDE). Per realizzare il primo, fai la solita cavalcatura, ma non tutto ... solo le cose che ti servono

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

(il motivo per non farlo - /worker/node_modulesè perché docker-compose manterrà quel volume tra le esecuzioni, il che significa che puoi divergere da ciò che è effettivamente nell'immagine (sconfiggendo lo scopo di non solo legare il montaggio dal tuo host)).

Il secondo è in realtà più difficile. La mia soluzione è un po 'hacker, ma funziona. Ho uno script per installare la cartella node_modules sul mio computer host e devo solo ricordarmi di chiamarlo ogni volta che aggiorno package.json (o, aggiungerlo al target make che esegue localmente docker-compose build).

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

2

Secondo me, non dovremmo RUN npm installnel Dockerfile. Invece, possiamo avviare un contenitore usando bash per installare le dipendenze prima di eseguire il servizio nodo formale

docker run -it -v ./app:/usr/src/app  your_node_image_name  /bin/bash
root@247543a930d6:/usr/src/app# npm install

In realtà sono d'accordo con te su questo. I volumi sono pensati per essere utilizzati quando si desidera condividere i dati tra contenitore e host. Quando decidi di node_modulesessere persistente anche dopo la rimozione del contenitore, dovresti anche sapere quando o quando non eseguire npm installmanualmente. OP suggerisce di farlo su ogni build di immagine . Puoi farlo, ma non è necessario utilizzare anche un volume per quello. Ad ogni build, i moduli saranno sempre aggiornati.
phil294,

@Blauhirn è utile montare un volume host locale nel contenitore quando si fa ad esempio gulp watch (o comandi analoghi) - si desidera che il nodo_moduli persista pur consentendo modifiche ad altre fonti (js, css ecc.). npm insiste sull'uso di un gulp locale, quindi deve persistere (o essere installato tramite altri metodi all'avvio)
tbm

2

Puoi provare qualcosa del genere nel tuo Dockerfile:

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

Quindi dovresti usare il Volume in questo modo:

volumes:
  - worker/:/worker:rw

Il codice di avvio dovrebbe far parte del repository di lavoro e ha il seguente aspetto:

#!/bin/sh
npm install
npm start

Quindi i node_modules fanno parte del volume di lavoro e vengono sincronizzati e gli script npm vengono eseguiti quando tutto è attivo.


Ciò aggiungerà un grande sovraccarico all'avvio del contenitore.
martedì

2
Ma solo la prima volta perché node_modules sarà persistito sul computer locale.
Parav01d

O fino a quando l'immagine non viene ricostruita o il volume rimosso :). Detto questo, non ho trovato una soluzione migliore.
martedì

0

Puoi anche abbandonare il tuo Dockerfile, per la sua semplicità, basta usare un'immagine di base e specificare il comando nel tuo file di composizione:

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

Questo è particolarmente utile per me, perché ho solo bisogno dell'ambiente dell'immagine, ma opero sui miei file all'esterno del contenitore e penso che sia quello che vuoi fare anche tu.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.