Docker-compose:npm安装成功后,卷中不存在node_modules


182

我有一个具有以下服务的应用程序:

  • web/ -在端口5000上保存并运行python 3 flask网络服务器。使用sqlite3。
  • worker/-有一个index.js文件,它是队列的工作程序。Web服务器使用json API通过端口与此队列进行交互9730。工作人员使用Redis进行存储。工作人员还将数据本地存储在文件夹中worker/images/

现在这个问题只涉及到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

当我运行时docker-compose build,一切都按预期工作,并且所有npm模块均按预期安装/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>

但是当我这样做时docker-compose up,我看到了这个错误:

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)

原来,这些模块都不存在/worker/node_modules(在主机上或在容器中)。

如果在主机上,我 npm install,则一切正常。但是我不想那样做。我希望容器处理依赖关系。

这是怎么了

(不用说,所有软件包都在中package.json。)


您最终找到了解决方案吗?
贾斯汀·斯坦顿

我想你应该使用ONBUILD指令......像这样:github.com/nodejs/docker-node/blob/master/0.12/onbuild/...
卢卡斯Pottersky

1
当IDE不知道node_module依赖性时,您将如何在主机上进行开发?
安德烈·

2
尝试volumes: - worker/:/worker/docker-compose.yml文件中删除块。此行将覆盖您使用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.-你怎么检查的?
Vallie

Answers:


271

发生这种情况是因为您已将worker目录作为卷添加到docker-compose.yml,因为在构建过程中未挂载该卷。

当docker构建映像时,将在node_modules目录中创建worker目录,并将所有依赖项安装在该目录中。然后在运行时,将worker来自外部docker 的目录装入docker实例(未安装node_modules),隐藏node_modules刚刚安装的目录。您可以通过从中删除已装入的卷来验证这一点docker-compose.yml

一种解决方法是使用数据卷存储所有node_modules,因为在worker安装目录之前,数据卷会从已构建的docker映像复制到数据中。可以这样完成docker-compose.yml

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

我不确定这是否会对映像的可移植性造成任何问题,但是由于您似乎主要是在使用docker提供运行时环境,因此这不是问题。

如果您想了解有关卷的更多信息,请在此处找到一份不错的用户指南:https : //docs.docker.com/userguide/dockervolumes/

编辑:Docker从那以后改变了它的语法,要求./相对于docker-compose.yml文件的文件中需要一个引导。


7
好的答案,最少的干扰,很好的解释!
kkemple

40
我尝试过这种方法,并在依赖项发生变化时碰壁。我已经重建了映像,开始了新的容器和卷/worker/node_modules与以前一样(具有旧的依赖项)。有什么技巧在重建映像时如何使用新卷?
OndrejSlinták16年

11
似乎docker compose不会删除其他容器使用的卷(即使它们已死)。因此,如果存在某些相同类型的死容器(无论出于何种原因),那么我在前面的评论中描述的情形将随之发生。从我的尝试来看,使用docker-compose rm似乎可以解决此问题,但是我相信必须有一个更好,更轻松的解决方案。
OndrejSlinták16年

14
2018年是否有解决方案,而不必rebuild --no-cache每次更换部门?
Eelke '18年

7
您现在可以使用`--renew-anon-volumes`来重新创建匿名卷,而不是先前容器中的数据。
Mohammed Essehemy

37

node_modules文件夹将被该卷覆盖,并且在容器中无法再访问。我正在使用本机模块加载策略从卷中取出文件夹:

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

node_modules无法从容器外部访问该目录,因为该目录已包含在映像中。


1
这种方法有什么缺点吗?似乎对我来说很好。
Bret Fisher

1
node_modules无法从容器外部访问,但实际上不是缺点;)
jsan

每次更改package.json时,都需要使用--no-cache来重建整个容器,对吗?
路加福音

是的,您需要在更改package.json时重建映像,但是--no-cache不是必需的。如果运行,docker-compose run app npm install您将在当前目录中创建一个node_modules,并且您不再需要重建映像。
jsan

9
缺点是:没有更多的IDE自动完成功能,没有帮助,没有良好的开发经验。现在,所有内容也都需要安装在主机上,但这不是使用docker的原因,即开发主机不需要任何东西就能使用项目吗?
Michael B.

31

@FrederikNS提供的解决方案可以使用,但是我更喜欢显式命名我的node_modules卷。

我的project/docker-compose.yml档案(docker-compose 1.6+版本):

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

我的文件结构是:

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

它创建一个名为的卷,project_node_modules并在每次启动应用程序时重新使用它。

我的docker volume ls样子是这样的:

DRIVER              VOLUME NAME
local               project1_mysql
local               project1_node_modules
local               project2_postgresql
local               project2_node_modules

3
虽然这个“可行”,但您在docker中规避了一个真正的概念:应植入所有依赖项以实现最大的可移植性。您必须在不运行其他命令的情况下才能移动此图像
Javier Buzzi

4
试图用几个小时来解决相​​同的问题,并提出了自己的相同解决方案。您的答案应该是评分最高的,但是我想ppl无法得到您的答案,因为您将卷命名为“ node_modules”,并且所有读者都错过了创建新卷的事实。在阅读代码时,我认为您只是“重新挂载”了语言环境的node_modules文件夹而放弃了这个想法。也许您应该将卷名编辑为类似“ container_node_modules”的名称,以使其清楚。:)
Fabian

1
我还发现这是最优雅的解决方案,可以轻松地在多个构建阶段中为节点模块引用相同的卷。很棒的文章《在Docker中构建Node Apps的经验教训》也使用了相同的方法。
kf06925

1
@ kf06925老兄,您真的救了我,我花了数小时试图解决这个问题,并感谢我能提供的文章!如果可以的话,我会为您买啤酒
Helado

21

我最近有一个类似的问题。您可以node_modules在其他地方安装并设置NODE_PATH环境变量。

在下面的示例中,我安装node_modules/install

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

6
顶部投票通过@FrederikNS解决方案是有用的,是我如何解决我的本地卷覆盖容器的不同期的node_modules基础上这篇文章。但这使我遇到了这个问题。这里的解决方案是创建一个单独的目录以将您的目录复制package.json到该目录中npm install,然后在其中运行,然后在其中指定NODE_PATH环境变量docker-compose.yml以指向node_modules该目录的文件夹,这样可以正常工作。
cwnewhouse

经过数小时的尝试,最终在我的Dockerfile中包含ENV NODE_PATH = / install / node_modules才是我的解决方案。谢谢你,先生。
Benny Meade

如果您npm install在主机上运行该怎么办?它似乎node_modules会出现在主机上,并将反映在容器上,优先于NODE_PATH。因此,容器将使用主机中的node_modules。
vitalets

18

有一个优雅的解决方案:

只是挂载不是整个目录,而是仅挂载应用程序目录。这样,您就不会遇到麻烦npm_modules

例:

  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

辉煌。无法理解为什么它不是可接受的答案。
吉万

2
这是一个很好且快速的解决方案,但是在安装新的依赖项后需要重新构建和修剪。
库努克,

我现在以不同的方式进行操作,应该有一个专门用于node_modules的卷和一个从主机挂载的卷。那就根本没有问题
霍姆斯

13

更新:使用解决方案 @FrederikNS提供。

我遇到了同样的问题。当文件夹/worker安装到容器上时-它的所有内容都将被同步化(因此,如果您本地没有node_modules文件夹,它将消失。)

由于基于OS的npm软件包不兼容,我不能只在本地安装模块-然后启动容器,所以..

我对此的解决方案是将源包装在一个src文件夹中,然后node_modules使用此index.js文件链接到该文件夹。因此,该index.js文件现在是我的应用程序的起点。

运行容器时,我将该/app/src文件夹安装到了本地src文件夹中。

所以容器文件夹看起来像这样:

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

这是丑陋的,但它的工作原理..


3
哦,亲爱的主...我不敢相信我也坚持下去!
卢卡斯·波特斯基2015年


7

将容器中的node_modules安装到与项目文件夹不同的容器中,并将NODE_PATH设置到您的node_modules文件夹中对我有帮助(您需要重建容器)。

我正在使用docker-compose。我的项目文件结构:

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

nodejs文件夹中的Dockerfile:

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
这是我找到的最佳解决方案。NODE_PATH是我的关键。
cdignam

我认为这很有意义,但是通过设置NODE_PATH来运行图像时, CMD npm start 它不使用指定的NODE_PATH。
Acton

6

还有一些简单的解决方案,无需将node_module目录映射到另一个卷。即将把安装npm软件包移到最终的CMD命令中。

这种方法的缺点:

  • 运行npm install每次运行容器时间(从开关npmyarn也可能加快这一进程中位)。

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

3

对于节点开发环境,我看到两个单独的要求...将源代码安装到容器中,并从容器中安装node_modules(对于您的IDE)。要完成第一个任务,您需要执行通常的安装,但不执行所有操作...仅执行所需的操作

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

(不这样做的原因 - /worker/node_modules是因为docker-compose会在两次运行之间保留该卷,这意味着您可以与映像中的实际内容有所不同(违背了不仅仅从主机上绑定挂载的目的))。

第二个实际上更难。我的解决方案有点骇人听闻,但确实有效。我有一个脚本可以在主机上安装node_modules文件夹,并且只记得在更新package.json(或将其添加到在本地运行docker-compose build的make目标)上调用它即可。

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

2

我认为我们不应该RUN npm install在Dockerfile中。相反,我们可以在运行正式节点服务之前使用bash启动容器以安装依赖项

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

实际上,我对此表示同意。卷是要在容器和主机之间共享数据时使用的。当您决定node_modules即使在删除容器后仍保持持久性时,您也应该知道何时或何时不进行npm install手动操作。OP建议在每次构建映像时都这样做。您可以做到这一点,但不需要为此使用一个卷。在每个构建中,模块将是最新的。
phil294

@Blauhirn在执行例如gulp watch(或类似命令)时将本地主机卷安装到容器中很有帮助-您希望持久化node_modules,同时仍然允许更改其他源(js,css等)。npm坚持使用本地gulp,因此必须持久(或在启动时通过其他方法安装)
tbm19年

2

您可以在Dockerfile中尝试以下操作:

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

然后,您应该像这样使用Volume:

volumes:
  - worker/:/worker:rw

起始脚本应该是您的工作程序存储库的一部分,如下所示:

#!/bin/sh
npm install
npm start

因此,node_modules是您的工作卷的一部分,并且会同步,并且在一切正常后执行npm脚本。


这将增加启动容器的大量开销。
tbm19年

2
但是只有第一次,因为node_modules将被持久保存在本地计算机上。
Parav01d

或直到重建映像或删除卷为止:)。也就是说,我自己还没有找到更好的解决方案。
tbm19年

0

由于其简单性,您还可以抛弃Dockerfile,仅使用基本映像并在撰写文件中指定命令:

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

这对我来说特别有用,因为我只需要映像的环境,但是要对容器外部的文件进行操作,所以我认为这也是您想要做的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.