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

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

  • web/ -在端口5000上持有并运行一个python3 flask web服务器。使用sqlite3。
  • worker/ -有一个index.js文件,它是队列的worker。web服务器通过端口9730使用json API与这个队列交互。worker使用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中。)

184856 次浏览

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

我也遇到过同样的问题。当文件夹/worker被挂载到容器时——它的所有内容都将被同步(所以如果你在本地没有node_modules文件夹,它将会消失)。

由于不兼容的npm包基于操作系统,我不能只是在本地安装模块-然后启动容器,所以..

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

当我运行容器时,我将/app/src文件夹挂载到本地src文件夹。

容器文件夹看起来是这样的:

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

它是丑陋的,但它工作。

由于Node.js加载模块的方式node_modules可以在源代码路径中的任何位置。例如,把你的源代码放在/worker/src,把你的package.json放在/worker,所以/worker/node_modules是它们被安装的地方。

这是因为你已经将你的worker目录作为卷添加到你的docker-compose.yml中,因为在构建期间卷没有被挂载。

当docker构建映像时,node_modules目录将在worker目录中创建,并且所有依赖项都安装在那里。然后在运行时,来自外部docker的worker目录被挂载到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文件。

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目录不能从容器外部访问,因为它包含在映像中。

我最近也遇到了类似的问题。你可以在其他地方安装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

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

我的project/docker-compose.yml文件(docker-compose version 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               project_mysql
local               project_node_modules
local               project2_postgresql
local               project2_node_modules

对于节点开发环境,我看到了两个不同的需求……将源代码挂载到容器中,并从容器中挂载node_modules(用于IDE)。要完成第一个,你需要做常规的坐骑,但不是所有的坐骑……正是你需要的东西

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

(不执行- /worker/node_modules的原因是因为docker-compose将在运行之间持久化该卷,这意味着您可以偏离映像中的实际内容(违背了不仅从主机绑定挂载的目的))。

第二题实际上更难。我的解决方案有点生硬,但很有效。我有一个脚本安装node_modules文件夹在我的主机上,我只需要记得调用它每当我更新包。Json(或者,将其添加到本地运行docker-compose build的make目标中)。

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

在容器中安装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

Dockerfile在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

有一个优雅的解决方案:

仅挂载应用程序目录,而不是整个目录。这样你就不会在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

还有一些简单的解决方案,不需要将node_module目录映射到另一个卷。它将把安装npm包移动到最后的CMD命令中。

这种方法的缺点:

  • 每次运行container时都要运行npm install(从npm切换到yarn也可能会稍微加快这个过程)。

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

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

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

你可以在Dockerfile中尝试这样做:

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

然后你应该像这样使用音量:

volumes:
- worker/:/worker:rw

启动脚本应该是你的工作库的一部分,看起来像这样:

#!/bin/sh
npm install
npm start

因此,node_modules是你的工作卷的一部分,并被同步,当一切就绪时,npm脚本将被执行。

你也可以放弃Dockerfile,因为它很简单,只需要使用一个基本的图像,并在你的compose文件中指定命令:

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

这对我来说特别有用,因为我只需要图像的环境,但在容器之外操作我的文件,我认为这也是您想要做的。

如果你希望在开发期间主机可以使用node_modules文件夹,你可以在启动容器时安装依赖项,而不是在构建时安装。我这样做是为了让语法高亮显示在编辑器中工作。

Dockerfile

# We're using a multi-stage build so that we can install dependencies during build-time only for production.


# dev-stage
FROM node:14-alpine AS dev-stage
WORKDIR /usr/src/app
COPY package.json ./
COPY . .
# `yarn install` will run every time we start the container. We're using yarn because it's much faster than npm when there's nothing new to install
CMD ["sh", "-c", "yarn install && yarn run start"]


# production-stage
FROM node:14-alpine AS production-stage
WORKDIR /usr/src/app
COPY package.json ./
RUN yarn install
COPY . .

.dockerignore

node_modules添加到.dockerignore,以防止它在Dockerfile运行COPY . .时被复制。我们使用卷来引入node_modules

**/node_modules

docker-compose.yml

node_app:
container_name: node_app
build:
context: ./node_app
target: dev-stage # `production-stage` for production
volumes:
# For development:
#   If node_modules already exists on the host, they will be copied
#   into the container here. Since `yarn install` runs after the
#   container starts, this volume won't override the node_modules.
- ./node_app:/usr/src/app
# For production:
#
- ./node_app:/usr/src/app
- /usr/src/app/node_modules

如果你不使用docker-compose你可以这样做:

FROM node:10


WORKDIR /usr/src/app


RUN npm install -g @angular/cli


COPY package.json ./
RUN npm install


EXPOSE 5000


CMD ng serve --port 5000 --host 0.0.0.0

然后你构建它:docker build -t myname .并通过添加两个卷运行它,第二个没有源的:docker run --rm -it -p 5000:5000 -v "$PWD":/usr/src/app/ -v /usr/src/app/node_modules myname

你可以将node_modules移动到/文件夹中。

工作原理

FROM node:0.12


WORKDIR /worker


COPY package.json /worker/
RUN npm install \
&& mv node_modules /node_modules


COPY . /worker/

我尝试了本页上最流行的答案,但遇到了一个问题:我的Docker实例中的node_modules目录将被缓存在命名或未命名的挂载点中,随后将覆盖作为Docker构建过程的一部分构建的node_modules目录。因此,我添加到package.json中的新模块将不会显示在Docker实例中。

幸运的是,我找到了这个优秀的页面,它解释了正在发生的事情,并提供了至少3种方法来解决它: https://burnedikt.com/dockerized-node-development-and-mounting-node-volumes/ < / p >

使用Yarn,您可以通过设置将node_modules移到卷之外

# ./.yarnrc
--modules-folder /opt/myproject/node_modules

看到https://www.caxy.com/blog/how-set-custom-location-nodemodules-path-yarn