Docker-compose: node_modules não presente em um volume após a instalação do npm ter êxito

184

Eu tenho um aplicativo com os seguintes serviços:

  • web/ - mantém e executa um servidor web python 3 flask na porta 5000. Usa sqlite3.
  • worker/- possui um index.jsarquivo que é um trabalhador para uma fila. o servidor da web interage com essa fila usando uma API json over port 9730. O trabalhador usa redis para armazenamento. O trabalhador também armazena dados localmente na pastaworker/images/

Agora, esta questão diz respeito apenas ao 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 executo docker-compose build, tudo funciona como esperado e todos os módulos npm são instalados /worker/node_modulescomo eu esperava.

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>

Mas quando o faço docker-compose up, vejo este erro:

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)

Acontece que nenhum dos módulos está presente /worker/node_modules(no host ou no contêiner).

Se no host, eu npm install, então tudo funciona muito bem. Mas eu não quero fazer isso. Eu quero que o contêiner manipule dependências.

O que está acontecendo de errado aqui?

(Escusado será dizer que todos os pacotes estão dentro package.json.)

KGo
fonte
Você acabou encontrando uma solução?
precisa
Eu acho que você deve usar a instrução ONBUILD ... Como esta: github.com/nodejs/docker-node/blob/master/0.12/onbuild/...
Lucas Pottersky
1
Como você faria o desenvolvimento no host quando o IDE não conhece as dependências node_module?
André
2
Tente remover o volumes: - worker/:/worker/bloco do docker-compose.ymlarquivo. Esta linha substitui a pasta que você cria com o comando 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.- Como você checou isso?
Vallie 16/06

Respostas:

272

Isso acontece porque você adicionou seu workerdiretório como um volume ao seu docker-compose.yml, pois o volume não é montado durante a compilação.

Quando o docker cria a imagem, o node_modulesdiretório é criado dentro do workerdiretório e todas as dependências são instaladas nele. Em tempo de execução, o workerdiretório de docker externo é montado na instância do docker (que não possui o instalado node_modules), ocultando o que node_modulesvocê acabou de instalar. Você pode verificar isso removendo o volume montado do seu docker-compose.yml.

Uma solução alternativa é usar um volume de dados para armazenar todos os node_modulesdados, pois os volumes de dados são copiados nos dados da imagem da janela de encaixe incorporada antes da workermontagem do diretório. Isso pode ser feito da docker-compose.ymlseguinte maneira:

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

Não tenho certeza se isso impõe problemas à portabilidade da imagem, mas como parece que você está usando o docker principalmente para fornecer um ambiente de tempo de execução, isso não deve ser um problema.

Se você quiser ler mais sobre volumes, há um bom guia do usuário disponível aqui: https://docs.docker.com/userguide/dockervolumes/

EDIT: Desde então, o Docker mudou sua sintaxe para exigir uma ./montagem para arquivos relacionados ao arquivo docker-compose.yml.

FrederikNS
fonte
7
ótima resposta, menos intrusiva e ótima explicação!
Kkemple
41
Eu tentei esse método e bati na parede quando as dependências mudaram. Reconstruí a imagem, iniciei um novo contêiner e o volume /worker/node_modulespermaneceu o mesmo de antes (com dependências antigas). Existe algum truque para usar o novo volume ao reconstruir a imagem?
Ondrej Slinták
11
Parece que a composição do docker não remove volumes se outros contêineres os usarem (mesmo que estejam mortos). Portanto, se houver alguns contêineres mortos do mesmo tipo (por qualquer motivo), o cenário que descrevi no comentário anterior segue. Pelo que tentei, usar docker-compose rmparece corrigir esse problema, mas acredito que deve haver uma solução melhor e mais fácil.
Ondrej Slinták
15
Existe uma solução em 2018 sem precisar rebuild --no-cachesempre que os deps mudam?
Eelke
7
agora você pode usar `--renew-anon-volumes` que recriará volumes anônimos em vez de dados dos contêineres anteriores.
Mohammed Essehemy
37

A node_modulespasta é substituída pelo volume e não é mais acessível no contêiner. Estou usando a estratégia de carregamento do módulo nativo para remover a pasta do 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/

O node_modulesdiretório não está acessível de fora do contêiner porque está incluído na imagem.

jsan
fonte
1
existem desvantagens nessa abordagem? Parece funcionar bem para mim.
Bret Fisher
1
node_modulesnão é acessível a partir de fora do recipiente, mas não é realmente uma desvantagem;)
JSAN
e toda vez que você altera o package.json, você precisa reconstruir todo o contêiner com --no-cache, certo?
Luke
Você precisa reconstruir a imagem quando alterar package.json, sim, mas --no-cache não é necessário. Se você executar docker-compose run app npm install, criará um node_modules no diretório atual e não precisará mais reconstruir a imagem.
JSAN
9
A desvantagem é: não há mais preenchimento automático do IDE, nenhuma ajuda, nenhuma boa experiência com o desenvolvedor. Tudo também precisa ser instalado no host agora, mas não é a razão para usar o docker aqui que o dev-host não precisa de nada para poder trabalhar com um projeto?
Michael B.
31

A solução fornecida pelo @FrederikNS funciona, mas eu prefiro nomear explicitamente meu volume node_modules.

Meu project/docker-compose.ymlarquivo (docker-compose versão 1.6+):

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

minha estrutura de arquivos é:

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

Ele cria um volume nomeado project_node_modulese o reutiliza toda vez que eu faço meu aplicativo.

Minha docker volume lsaparência é assim:

DRIVER              VOLUME NAME
local               project1_mysql
local               project1_node_modules
local               project2_postgresql
local               project2_node_modules
Guillaume Vincent
fonte
3
Enquanto isso "funciona", você está contornando um conceito real no docker: todas as dependências devem ser incorporadas para máxima portabilidade. você não pode mover esta imagem sem a execução de outros comandos que sopra meio
Javier Buzzi
4
tentei resolver o mesmo problema por horas e veio com a mesma solução. Sua resposta deve ter a classificação mais alta, mas eu acho que as pessoas não obtêm sua resposta porque você nomeou seu volume "node_modules" e todos os leitores estão perdendo o fato de que isso cria um novo volume. Ao ler seu código, pensei que você apenas "remontou" a pasta locale node_modules e descartou essa ideia. Talvez você deva editar o nome do volume para algo como "container_node_modules" para deixar isso claro. :)
Fabian
1
Também achei que essa era a solução mais elegante, o que permite facilmente consultar o mesmo volume para módulos de nós em vários estágios de construção. O excelente artigo Lições do Building Node Apps no Docker também usa a mesma abordagem.
kf06925 14/01
1
@ kf06925 cara você realmente me salvou, passei horas tentando resolver isso e graças ao artigo que pude !! Eu compraria uma cerveja para você, se eu pudesse agradecer tanto
helado
21

Recentemente, tive um problema semelhante. Você pode instalar em node_modulesoutro lugar e definir a NODE_PATHvariável de ambiente.

No exemplo abaixo eu instalado node_modulesem/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
fonte
6
A solução mais votada pelo @FrederikNS é útil: foi como resolvi um problema diferente do meu volume local substituindo o contêiner com node_modulesbase neste artigo . Mas isso me levou a experimentar esse problema . Esta solução aqui de criar um diretório separado para copiar package.json, executar npm installlá e especificar a NODE_PATHvariável de ambiente docker-compose.ymlpara apontar para a node_modulespasta desse diretório funciona e parece certo.
Cwewhouse
Incluir ENV NODE_PATH = / install / node_modules no Dockerfile foi finalmente a solução para mim depois de horas tentando diferentes abordagens. Obrigado senhor.
Benny Meade
E se você executar npm installno host? Parece node_modulesque aparecerá no host e será refletido no contêiner, tendo prioridade NODE_PATH. Portanto, o contêiner usará node_modules do host.
vitalets 6/06
19

Há uma solução elegante:

Apenas monte não o diretório inteiro, mas apenas o diretório do aplicativo. Dessa forma, você não terá problemas comnpm_modules .

Exemplo:

  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
fonte
Brilhante. Não consigo entender por que não é a resposta aceita.
Jivan
2
Essa é uma solução boa e rápida, mas requer reconstruções e remoção após nova instalação de dependência.
Kunok 5/05/19
Eu faço isso de forma diferente agora, deve haver um volume especificamente para node_modules e um volume que você monta do host. Então não há nenhum problema
holms
14

ATUALIZAÇÃO: use a solução fornecida pelo @FrederikNS.

Eu encontrei o mesmo problema. Quando a pasta/worker é montada no contêiner - todo o seu conteúdo será sincronizado (portanto, a pasta node_modules desaparecerá se você não a tiver localmente.)

Devido a pacotes npm incompatíveis baseados no sistema operacional, eu não podia apenas instalar os módulos localmente - em seguida, iniciar o contêiner, então ..

Minha solução para isso foi agrupar a fonte em uma srcpasta e vincular node_modulesa ela usando esse arquivo index.js . Portanto, o index.jsarquivo agora é o ponto de partida do meu aplicativo.

Quando executo o contêiner, montei a /app/srcpasta no meu localsrc pasta .

Portanto, a pasta contêiner se parece com isso:

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

É feio , mas funciona ..

GELÉIA
fonte
3
oh, querido senhor ... não acredito que também estou preso a isso!
Lucas Pottersky
10

Devido à maneira como o Node.js carrega módulos , node_modulespode estar em qualquer lugar do caminho para o seu código-fonte. Por exemplo, colocar a sua fonte no /worker/srce sua package.jsonem /worker, por isso /worker/node_modulesé o lugar onde eles estão instalados.

Justin Stayton
fonte
7

Instalar node_modules no container para diferente da pasta do projeto e definir NODE_PATH para a pasta node_modules me ajuda (é necessário reconstruir o container).

Estou usando o docker-compose. Minha estrutura de arquivos do projeto:

-/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 na pasta 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
fonte
1
Esta é a melhor solução que encontrei. NODE_PATHfoi a chave para mim.
Cdignam
Eu acho que isso faz sentido, mas definindo NODE_PATH, quando executar a imagem, CMD npm start não usa o NODE_PATH especificado.
Acton
6

Também há uma solução simples sem mapear o node_modulediretório para outro volume. Está prestes a mover a instalação de pacotes npm para o comando final do CMD.

Desvantagem desta abordagem:

  • execute npm installcada vez que você executar o contêiner (alternar npmpara yarntambém pode acelerar um pouco esse processo).

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

Existem dois requisitos separados que eu vejo para ambientes de desenvolvimento de nós ... monte seu código-fonte no contêiner e monte os node_modules a partir do contêiner (para o seu IDE). Para realizar o primeiro, você faz a montagem habitual, mas não tudo ... apenas as coisas que você precisa

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

(a razão para não fazer - /worker/node_modules é porque o docker-compose manterá esse volume entre as execuções, o que significa que você pode divergir do que realmente está na imagem (derrotando o objetivo de não apenas ligar a montagem do host)).

O segundo é realmente mais difícil. Minha solução é um pouco imprudente, mas funciona. Eu tenho um script para instalar a pasta node_modules na minha máquina host e só lembro de chamá-la sempre que atualizar o package.json (ou adicioná-lo ao make target que executa a compilação docker-compose localmente).

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

Na minha opinião, não devemos RUN npm installno Dockerfile. Em vez disso, podemos iniciar um contêiner usando o bash para instalar as dependências antes de executar o serviço formal do nó

docker run -it -v ./app:/usr/src/app  your_node_image_name  /bin/bash
root@247543a930d6:/usr/src/app# npm install
salamandra
fonte
Na verdade, eu concordo com você neste caso. Os volumes devem ser usados ​​quando você deseja compartilhar os dados entre o contêiner e o host. Ao decidir manter sua node_modulespersistência mesmo após a remoção do contêiner, você também deve saber quando ou não fazer npm installmanualmente. O OP sugere fazê-lo em todas as compilações de imagens . Você pode fazer isso, mas também não precisa usar um volume para isso. Em cada compilação, os módulos estarão atualizados a qualquer momento.
phil294
@ Blahhirn é útil montar um volume de host local no contêiner ao fazer, por exemplo, gulp watch (ou comandos análogos) - você deseja que o node_modules persista enquanto ainda permite alterações em outras fontes (js, css etc.). npm insiste em usar um gole local, por isso tem que persistem (ou ser instalado através de outros métodos em start-up)
tbm
2

Você pode tentar algo assim no seu Dockerfile:

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

Então você deve usar o volume assim:

volumes:
  - worker/:/worker:rw

O script de inicialização deve fazer parte do seu repositório de trabalho e fica assim:

#!/bin/sh
npm install
npm start

Portanto, o node_modules faz parte do volume do seu trabalhador e é sincronizado e os scripts npm são executados quando tudo estiver funcionando.

Parav01d
fonte
Isso adicionará uma grande sobrecarga à inicialização do contêiner.
tbm
2
Mas apenas na primeira vez, porque os node_modules serão mantidos na máquina local.
precisa saber é o seguinte
Ou até que a imagem seja reconstruída ou o volume removido :). Dito isto, eu não encontrei uma solução melhor.
tbm
0

Você também pode abandonar o Dockerfile, devido à sua simplicidade, basta usar uma imagem básica e especificar o comando no seu arquivo de composição:

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

Isso é particularmente útil para mim, porque eu só preciso do ambiente da imagem, mas opere nos meus arquivos fora do contêiner e acho que é isso que você deseja fazer também.

itmuckel
fonte