Docker-compose: node_modules no está presente en un volumen después de que la instalación de npm se realice correctamente

184

Tengo una aplicación con los siguientes servicios:

  • web/ - mantiene y ejecuta un servidor web de matraz python 3 en el puerto 5000. Utiliza sqlite3.
  • worker/- tiene un index.jsarchivo que es un trabajador para una cola. el servidor web interactúa con esta cola utilizando una API json a través del puerto 9730. El trabajador usa redis para el almacenamiento. El trabajador también almacena datos localmente en la carpetaworker/images/

Ahora esta pregunta solo concierne a la 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

Cuando ejecuto docker-compose build, todo funciona como se esperaba y todos los módulos npm se instalan /worker/node_modulescomo esperaba.

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>

Pero cuando lo hago docker-compose up, veo este error:

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)

Resulta que ninguno de los módulos está presente en /worker/node_modules(en el host o en el contenedor).

Si en el host, yo npm install, entonces todo funciona bien. Pero no quiero hacer eso. Quiero que el contenedor maneje las dependencias.

¿Qué está pasando mal aquí?

(No hace falta decir que todos los paquetes están incluidos) package.json.

KGo
fuente
¿Terminaste encontrando una solución?
Justin Stayton
Creo que debería usar la instrucción ONBUILD ... De esta manera: github.com/nodejs/docker-node/blob/master/0.12/onbuild/…
Lucas Pottersky
1
¿Cómo haría el desarrollo en el host cuando el IDE no conoce las dependencias node_module?
André
2
Intenta eliminar el volumes: - worker/:/worker/bloque del docker-compose.ymlarchivo. Esta línea sobrescribe la carpeta que crea con el comando COPIAR.
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.- ¿Cómo verificaste esto?
Vallie

Respuestas:

272

Esto sucede porque ha agregado su workerdirectorio como un volumen a su docker-compose.yml, ya que el volumen no se monta durante la compilación.

Cuando Docker construye la imagen, el node_modulesdirectorio se crea dentro del workerdirectorio y todas las dependencias se instalan allí. Luego, en tiempo de ejecución, el workerdirectorio desde el acoplador externo se monta en la instancia del acoplador (que no tiene el instalado node_modules), ocultando el node_modulesque acaba de instalar. Puede verificar esto quitando el volumen montado de su docker-compose.yml.

Una solución alternativa es utilizar un volumen de datos para almacenar todo node_modules, ya que los volúmenes de datos copian los datos de la imagen workeracoplada construida antes de montar el directorio. Esto se puede hacer de la docker-compose.ymlsiguiente manera:

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

No estoy completamente seguro de si esto impone algún problema para la portabilidad de la imagen, pero como parece que está utilizando principalmente Docker para proporcionar un entorno de tiempo de ejecución, esto no debería ser un problema.

Si desea leer más sobre los volúmenes, hay una buena guía de usuario disponible aquí: https://docs.docker.com/userguide/dockervolumes/

EDITAR: desde entonces Docker ha cambiado su sintaxis para requerir un ./inicio para el montaje en archivos relativos al archivo docker-compose.yml.

FrederikNS
fuente
77
¡Gran respuesta, menos intrusiva y gran explicación!
kkemple
41
Intenté este método y golpeé la pared cuando las dependencias cambiaron. Reconstruí la imagen, comencé un nuevo contenedor y el volumen se /worker/node_modulesmantuvo igual que antes (con dependencias antiguas). ¿Hay algún truco para usar el nuevo volumen al reconstruir la imagen?
Ondrej Slinták
11
Parece que Docker compose no elimina volúmenes si otros contenedores los usan (incluso si están muertos). Entonces, si hay algunos contenedores muertos del mismo tipo (por cualquier razón), el escenario que describí en el comentario anterior sigue. Por lo que he intentado, el uso docker-compose rmparece solucionar este problema, pero creo que debe haber una solución mejor y más fácil.
Ondrej Slinták
15
¿Hay una solución en 2018 sin tener que hacerlo rebuild --no-cachecada vez que cambian los departamentos?
Eelke
77
ahora puede usar `--renew-anon-volume` que recreará volúmenes anónimos en lugar de datos de los contenedores anteriores.
Mohammed Essehemy
37

El node_modulesvolumen sobrescribe la carpeta y ya no se puede acceder a ella en el contenedor. Estoy usando la estrategia de carga del módulo nativo para extraer la carpeta del volumen:

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

No node_modulesse puede acceder al directorio desde fuera del contenedor porque está incluido en la imagen.

jsan
fuente
1
¿Hay alguna desventaja en este enfoque? Parece que funciona bien para mí.
Bret Fisher
1
node_modulesno es accesible desde el exterior del contenedor pero no es realmente un inconveniente;)
jsan
y cada vez que cambie package.json necesita reconstruir todo el contenedor con --no-cache ¿verdad?
Lucas
Necesita reconstruir la imagen cuando cambia package.json, sí, pero --no-cache no es necesario. Si ejecuta docker-compose run app npm install, creará un node_modules en el directorio actual y ya no necesitará reconstruir la imagen.
jsan
9
La desventaja es: no más autocompletado IDE, sin ayuda, sin buena experiencia de desarrollo. Ahora también es necesario instalar todo en el host, pero ¿no es la razón para usar docker aquí que el dev-host no necesita nada para poder trabajar con un proyecto?
Michael B.
31

La solución proporcionada por @FrederikNS funciona, pero prefiero nombrar explícitamente mi volumen node_modules.

Mi project/docker-compose.ymlarchivo (docker-compose versión 1.6+):

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

mi estructura de archivos es:

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

Crea un volumen con nombre project_node_modulesy lo reutiliza cada vez que subo mi aplicación.

Mi docker volume lsaspecto es este:

DRIVER              VOLUME NAME
local               project1_mysql
local               project1_node_modules
local               project2_postgresql
local               project2_node_modules
Guillaume Vincent
fuente
3
Si bien esto "funciona", está eludiendo un concepto real en Docker: todas las dependencias deben integrarse para una máxima portabilidad. no puedes mover esta imagen sin ejecutar otros comandos que suenan un poco
Javier Buzzi
44
Traté de resolver el mismo problema durante horas y se me ocurrió la misma solución. Su respuesta debe ser la mejor calificada, pero supongo que las personas no reciben su respuesta porque nombró a su volumen "node_modules" y a todos los lectores les falta el hecho de que esto crea un nuevo volumen. Al leer su código, pensé que simplemente "volvería a montar" la carpeta locale node_modules y descarté esa idea. Tal vez deberías editar el nombre del volumen a algo como "container_node_modules" para que quede claro. :)
Fabian
1
También encontré que esta es la solución más elegante, que permite referirse fácilmente al mismo volumen para módulos de nodo en varias etapas de construcción. El excelente artículo Lecciones de las aplicaciones de nodo de construcción en Docker también utiliza el mismo enfoque.
kf06925
1
@ kf06925 hombre, realmente me salvaste, pasé horas tratando de resolver esto y gracias al artículo que pude !! Te compraría una cerveza si pudiera, muchas gracias
helado
21

Recientemente tuve un problema similar. Puede instalar en node_modulesotro lugar y establecer la NODE_PATHvariable de entorno.

En el siguiente ejemplo, instalé node_modulesen/install

trabajador / 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
fuente
66
La solución más votada por @FrederikNS es útil, fue cómo resolví un problema diferente de mi volumen local sobrescribiendo los contenedores en node_modulesfunción de este artículo . Pero me llevó a experimentar este problema . Esta solución aquí es crear un directorio separado para copiarlo package.json, ejecutarlo npm installallí, luego especificar la NODE_PATHvariable de entorno docker-compose.ymlpara que apunte a la node_modulescarpeta de ese directorio que funciona y se siente bien.
cwnewhouse
Incluir ENV NODE_PATH = / install / node_modules en Dockerfile fue finalmente la solución para mí después de horas de probar diferentes enfoques. Gracias Señor.
Benny Meade
¿Qué pasa si corres npm installen el host? Parece node_modulesque aparecerá en el host y se reflejará en el contenedor, teniendo prioridad sobre NODE_PATH. Por lo tanto, el contenedor usará node_modules del host.
vitalets
19

Hay una solución elegante:

Simplemente monte no todo el directorio, sino solo el directorio de la aplicación. De esta manera no tendrás problemas npm_modules.

Ejemplo:

  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
fuente
Brillante. No puedo entender por qué no es la respuesta aceptada.
Jivan el
2
Esta es una solución buena y rápida, pero requiere reconstrucciones y ciruelas pasas después de la instalación de una nueva dependencia.
Kunok
Lo hago de manera diferente ahora, debería haber un volumen específicamente para node_modules y un volumen que se monte desde el host. Entonces no hay ningún problema
holms
14

ACTUALIZACIÓN: utilice la solución proporcionada por @FrederikNS.

Encontré el mismo problema. Cuando la carpeta/worker se monta en el contenedor, todo su contenido se sincronizará (por lo que la carpeta node_modules desaparecerá si no la tiene localmente).

Debido a los paquetes npm incompatibles basados ​​en el sistema operativo, no pude simplemente instalar los módulos localmente, luego iniciar el contenedor, así que ...

Mi solución a esto fue envolver la fuente en una srccarpeta, luego vincularla node_modulesa esa carpeta, usando este archivo index.js . Entonces, el index.jsarchivo es ahora el punto de partida de mi aplicación.

Cuando ejecuto el contenedor, monté la /app/srccarpeta en mi localsrc carpeta .

Entonces la carpeta del contenedor se ve así:

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

Es feo , pero funciona.

MERMELADA
fuente
3
Oh, querido señor ... ¡No puedo creer que también estoy atrapado con eso!
Lucas Pottersky
10

Debido a la forma en que Node.js carga los módulos , node_modulespuede estar en cualquier parte de la ruta a su código fuente. Por ejemplo, poner su origen en /worker/srcy su package.jsonen /worker, por lo que /worker/node_moduleses donde están instalados.

Justin Stayton
fuente
7

Instalar node_modules en el contenedor para que sea diferente de la carpeta del proyecto, y configurar NODE_PATH en su carpeta node_modules me ayuda (necesita reconstruir el contenedor).

Estoy usando docker-compose. Estructura del archivo de mi proyecto:

-/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 en la carpeta 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
fuente
1
Esta es la mejor solución que he encontrado. NODE_PATHFue la clave para mí.
cdignam el
Creo que esto tiene sentido, pero al configurar NODE_PATH, cuando ejecuta la imagen, CMD npm start no usa el NODE_PATH especificado.
Acton
6

También hay una solución simple sin asignar el node_moduledirectorio a otro volumen. Está a punto de mover la instalación de paquetes npm al comando CMD final.

Desventaja de este enfoque:

  • ejecuta npm installcada vez que ejecutas el contenedor (cambiar de npma yarntambién podría acelerar un poco este proceso).

trabajador / 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
fuente
3

Hay dos requisitos separados que veo para los entornos de desarrollo de nodos ... monte su código fuente EN el contenedor y monte los node_modules DESDE el contenedor (para su IDE). Para lograr lo primero, haces el montaje habitual, pero no todo ... solo las cosas que necesitas

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

(la razón para no hacer - /worker/node_modules es porque docker-compose persistirá en ese volumen entre ejecuciones, lo que significa que puede diferir de lo que realmente está en la imagen (lo que anula el propósito de no solo vincular el montaje desde su host)).

El segundo es realmente más difícil. Mi solución es un poco hack, pero funciona. Tengo un script para instalar la carpeta node_modules en mi máquina host, y solo tengo que recordar llamarlo cada vez que actualizo package.json (o lo agrego al destino make que ejecuta la compilación docker-compose localmente).

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

En mi opinión, no deberíamos RUN npm installen el Dockerfile. En cambio, podemos iniciar un contenedor usando bash para instalar las dependencias antes de ejecutar el servicio de nodo formal

docker run -it -v ./app:/usr/src/app  your_node_image_name  /bin/bash
root@247543a930d6:/usr/src/app# npm install
salamandra
fuente
De hecho, estoy de acuerdo contigo en este caso. Los volúmenes están destinados a usarse cuando desea compartir los datos entre el contenedor y el host. Cuando decida que node_modulessea ​​persistente incluso después de retirar el contenedor, también debe saber cuándo o cuándo hacerlo npm installmanualmente. OP sugiere hacerlo en cada compilación de imagen . Puede hacerlo, pero no necesita usar también un volumen para eso. En cada compilación, los módulos estarán actualizados en cualquier momento.
phil294
@Blauhirn es útil montar un volumen de host local en el contenedor cuando se realiza, por ejemplo, gulp watch (o comandos análogos): desea que los node_modules persistan mientras aún permite cambios en otras fuentes (js, css, etc.). npm insiste en usar un trago local, por lo que tiene que persistir (o instalarse a través de otros métodos al inicio)
tbm
2

Puede probar algo como esto en su Dockerfile:

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

Entonces deberías usar el Volumen así:

volumes:
  - worker/:/worker:rw

El script de inicio debe ser parte de su repositorio de trabajo y se ve así:

#!/bin/sh
npm install
npm start

Entonces, los node_modules son parte de su volumen de trabajo y se sincronizan y los scripts npm se ejecutan cuando todo está activo.

Parav01d
fuente
Esto agregará una gran sobrecarga para iniciar el contenedor.
tbm
2
Pero solo la primera vez porque los node_modules se mantendrán en la máquina local.
Parav01d
O hasta que se reconstruya la imagen o se elimine el volumen :). Dicho esto, no he encontrado una solución mejor para mí.
tbm
0

También puede deshacerse de su Dockerfile, debido a su simplicidad, simplemente use una imagen básica y especifique el comando en su archivo de redacción:

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

Esto es particularmente útil para mí, porque solo necesito el entorno de la imagen, pero operar en mis archivos fuera del contenedor y creo que esto es lo que quieres hacer también.

itmuckel
fuente