node_modules 上的 docker-compose 卷但为空

Posted

技术标签:

【中文标题】node_modules 上的 docker-compose 卷但为空【英文标题】:docker-compose volume on node_modules but is empty 【发布时间】:2016-11-20 10:16:27 【问题描述】:

我是 Docker 的新手,我想在我的计算机上映射 node_modules 文件夹(用于调试目的)。

这是我的 docker-compose.yml

web:
  build: .
  ports:
    - "3000:3000"
  links:
    - db
  environment:
    PORT: 3000
  volumes:
    - .:/usr/src/app
    - /usr/src/app/node_modules
db:
  image: mongo:3.3
  ports:
    - "27017:27017"
  command: "--smallfiles --logpath=/dev/null"

我使用 Docker for Mac。当我运行docker-compose up -d 时,一切正常,但它在我的计算机上创建了一个 node_modules 文件夹,但它是空的。我进入容器的 bash 和 ls node_modules,所有的包都在那里。

如何在我的计算机上获取容器中的内容?

谢谢

【问题讨论】:

迈克,你找到解决办法了吗?我有同样的问题:我希望 node_modules 文件夹从容器镜像到主机,这样 WebStorm 可以看到依赖关系,但我所能做的就是在主机和容器上运行 npm install 我没有。对不起 好的,希望有人愿意领取赏金! :) 不错!谢谢! 迈克,@Alessandro,你能提供一些反馈吗?谢谢! 【参考方案1】:

最简单的解决方案

使用 Docker Compose 和带有 Bind Mount 的本地 Volume 驱动程序将 node_modules 卷配置为使用本地 node_modules 目录作为其存储位置。

首先,确保您有一个本地 node_modules 目录,或者创建它,然后在 docker-compose 文件的命名卷部分为它创建一个 Docker 卷:

volumes:
  node_modules:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: ./local/relative/path/to/node_modules

然后,将您的 node_modules 卷添加到您的服务中:

ui:
  volumes:
    - node_modules:/container/path/to/node_modules

只要确保您始终在 Docker 容器内进行 node_module 更改(使用 docker-compose exec),它将完美同步并在主机上用于 IDE、代码完成、调试等。

【讨论】:

谢谢,这正是我想要的。能否简单介绍一下volume driver_opts? @e-e 由于在这种情况下驱动程序设置为本地,因此 driver_opts 是本地驱动程序的选项。 “type”在这里没有,因为我们正在使用主机文件系统,否则它可以设置为“nfs”或“cifs”等。“o”,“opt”的缩写,又名“options”是一个逗号分隔驱动程序选项列表,在本例中为“绑定”以创建绑定挂载。而“设备”是卷的存储位置。 @shet_tayyy 您是否收到任何错误或任何其他可能有助于找出问题所在的反馈?我仍然使用这个解决方案,它工作得很好,所以它可能很愚蠢,比如路径不正确、配置中缺少字符或权限。 @JeremyM4n 我的错。我应该提供更多细节。在下面发布错误:Error response from daemon: failed to mount local volume: mount ./node_modules:/var/lib/docker/volumes/fastify-beej_node_modules/_data, flags: 0x1000: no such file or directory @shet_tayyy 需要先手动创建本地 node_modules 目录。如果它已经存在,请确保您的路径在设备设置中是正确的。它基于 docker-compose 文件的相对位置。【参考方案2】:

TL;DR 工作示例,克隆并尝试: https://github.com/xbx/base-server


您首先需要在您的计算机(外部映像)中有一个 node_modules 用于调试目的(在运行容器之前)。

如果你只想调试 node_modules:

volumes:
    - /path/to/node_modules:/usr/src/app/node_modules

如果你想同时调试你的代码和 node_modules:

volumes:
    - .:/usr/src/app/

请记住,您需要在容器外至少运行一次npm install(或复制docker build 生成的node_modules 目录)。如果您需要更多详细信息,请立即告诉我。


编辑。因此,在 OSX 中不需要 npm,您可以:

    docker build,然后是 docker cp <container-id>:/path/to/node-modules ./local-node-modules/。然后在你的 docker-compose.yml 中挂载这些文件并解决你想要的任何问题。 或者,docker build 和那里(Dockerfile)在另一个目录中执行npm install。然后在您的命令(CMD 或 docker-compose 命令)中将(cp)复制到正确的目录,但该目录从您的计算机(docker-compose.yml 中的一个卷)安装为空,然后解决您的任何问题想要。

编辑 2。(选项 2)工作示例,克隆并尝试: https://github.com/xbx/base-server 我在这个 repo 中自动完成了这一切。

Dockerfile

FROM node:6.3

# Install app dependencies
RUN mkdir /build-dir
WORKDIR /build-dir
COPY package.json /build-dir
RUN npm install -g babel babel-runtime babel-register mocha nodemon
RUN npm install

# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
RUN ln -s /build-dir/node_modules node_modules

# Bundle app source
COPY . /usr/src/app

EXPOSE 1234
CMD [ "npm", "start" ]

docker-compose.yml

web:
  build: .
  ports:
    - "1234:1234"
  links:
    - db # liaison avec la DB
  environment:
    PORT: 1234
  command: /command.sh
  volumes:
    - ./src/:/usr/src/app/src/
    - ./node_modules:/usr/src/app/node_modules
    - ./command.sh:/command.sh
db:
  image: mongo:3.3
  ports:
    - "27017:27017"
  command: "--smallfiles --logpath=/dev/null"

command.sh

#!/bin/bash

cp -r /build-dir/node_modules/ /usr/src/app/

exec npm start

请克隆我的 repo 并执行docker-compose up。它做你想做的事。 PS:可以改进以更好的方式做同样的事情(即最佳实践等)

我在 OSX 中,它对我有用。

【讨论】:

由于npm install 依赖于平台,在主机上运行它可能会导致跨平台问题(host=mac,container=debian)。 似乎您建议手动将npm install 结果复制到卷中。您是否有理由更喜欢手动而不是像我在答案中发布的那样作为构建和入口点的一部分自动执行? 当您装入空卷时,该符号链接是否有效?这开始看起来与我之前发布的答案非常相似。 我喜欢这个解决方案,我没有考虑在安装卷后使用 bash 脚本来复制 node_modules。非常感谢您的帮助! 有人用node_modules 解决了这个问题吗?由于可能存在跨平台问题,我不想将它们安装在我的主机上(@gesellix 在上面也写过)。是否可以在 Docker 容器中安装 node_modules 并将它们镜像到主机,以便我可以在需要时查看源代码,以便我的 IDE 可以看到所有的 devDependencies,如 eslint 和其他? 【参考方案3】:

我添加了@Robert's answer,因为有几件事没有考虑到它;即:

cp 耗时太长,用户无法查看进度。 如果node_modules 是通过主机安装的,我希望它被覆盖。 我希望能够在容器运行和未运行时git pull 并相应地更新node_modules,如果有任何更改。 我只希望在开发环境中出现这种行为。

为了解决第一个问题,我在我的图像上安装了rsync,以及pv(因为我想在删除的同时查看进度)。由于我使用的是alpine,所以我在Dockerfile中使用了apk add

# Install rsync and pv to view progress of moving and deletion of node_modules onto host volume.
RUN apk add rsync && apk add pv

然后我将entrypoint.sh 更改为如下所示(您可以将yarn.lock 替换为package-lock.json):

#!/bin/ash

# Declaring variables.
buildDir=/home/node/build-dir
workDir=/home/node/work-dir
package=package.json
lock=yarn.lock
nm=node_modules

#########################
# Begin Functions
#########################

copy_modules ()  # Copy all files of build directory to that of the working directory.
  echo "Calculating build folder size..."
  buildFolderSize=$( du -a $buildDir/$nm | wc -l )
  echo "Copying files from build directory to working directory..."
  rsync -avI $buildDir/$nm/. $workDir/$nm/ | pv -lfpes "$buildFolderSize" > /dev/null
  echo "Creating flag to indicate $nm is in sync..."
  touch $workDir/$nm/.docked # Docked file is a flag that tells the files were copied already from the build directory.


delete_modules ()  # Delete old module files.
    echo "Calculating incompatible $1 direcotry $nm folder size..."
    folderSize=$( du -a $2/$nm | wc -l )
    echo "Deleting incompatible $1 directory $nm folder..."
    rm -rfv $2/$nm/* | pv -lfpes "$folderSize" > /dev/null # Delete all files in node_modules.
    rm -rf $2/$nm/.* 2> /dev/null # Delete all hidden files in node_modules.node_modules.


#########################
# End Functions
# Begin Script
#########################

if cmp -s $buildDir/$lock $workDir/$lock >/dev/null 2>&1 # Compare lock files.
  then
    # Delete old modules.
    delete_modules "build" "$buildDir"
    # Remove old build package.
    rm -rf $buildDir/$package 2> /dev/null
    rm -rf $buildDir/$lock 2> /dev/null
    # Copy package.json from working directory to build directory.
    rsync --info=progress2 $workDir/$package $buildDir/$package
    rsync --info=progress2 $workDir/$lock $buildDir/$lock
    cd $buildDir/ || return
    yarn
    delete_modules "working" "$workDir"
    copy_modules

# Check if the directory is empty, as it is when it is mounted for the first time.
elif [ -z "$(ls -A $workDir/$nm)" ]
  then
    copy_modules
elif [ ! -f "$workDir/$nm/.docked" ] # Check if modules were copied from build directory.
  then
    # Delete old modules.
    delete_modules "working" "$workDir"
    # Copy modules from build directory to working directory.
    copy_modules
else
    echo "The node_modules folder is good to go; skipping copying."
fi

#########################
# End Script
#########################

if [ "$1" != "git" ] # Check if script was not run by git-merge hook.
  then
    # Change to working directory.
    cd $workDir/ || return
    # Run yarn start command to start development.
    exec yarn start:debug
fi

我添加了pv,至少可以向用户显示正在发生的事情的进度。另外,我添加了一个标志,表明node_modules 是通过容器安装的。

每当安装一个包时,我利用package.json 文件的postinstallpostuninstall 挂钩将package.jsonyarn.lock 文件从工作目录复制到构建目录,以使它们保持最新状态日期。我还安装了postinstall-postinstall 包以确保postuninstall 挂钩有效。

"postinstall"  : "if test $DOCKER_FLAG = 1; then rsync -I --info=progress2 /home/node/work-dir/package.json /home/node/build-dir/package.json && rsync -I --info=progress2 /home/node/work-dir/yarn.lock /home/node/build-dir/yarn.lock && echo 'Build directory files updated.' && touch /home/node/work-dir/node_modules/.docked; else rm -rf ./node_modules/.docked && echo 'Warning: files installed outside container; deleting docker flag file.'; fi",
"postuninstall": "if test $DOCKER_FLAG = 1; then rsync -I --info=progress2 /home/node/work-dir/package.json /home/node/build-dir/package.json && rsync -I --info=progress2 /home/node/work-dir/yarn.lock /home/node/build-dir/yarn.lock && echo 'Build directory files updated.' && touch /home/node/work-dir/node_modules/.docked; else rm -rf ./node_modules/.docked && echo 'Warning: files installed outside container; deleting docker flag file.'; fi",

我使用了一个名为DOCKER_FLAG 的环境变量,并在docker-compose.yml 文件中将其设置为1。这样,当有人在容器外安装时,它就不会运行。此外,我确保删除了 .docked 标志文件,以便脚本知道它已使用主机命令安装。

关于每次拉取时同步node_modules的问题,我用了一个git hook;即post-merge 钩子。每次拉动时,如果容器正在运行,它将尝试运行entrypoint.sh 脚本。它还将为脚本git 提供一个参数,该脚本检查是否不运行exec yarn:debug,因为容器已经在运行。这是我在.git/hooks/post-merge 的脚本:

#!/bin/bash

if [ -x "$(command -v docker)" ] && [ "$(docker ps -a | grep <container_name>)" ]
then
  exec docker exec <container_name> sh -c "/home/node/build-dir/entrypoint.sh git"
  exit 1
fi

如果容器没有运行,并且我获取了更改,那么entrypoint.sh 脚本将首先检查锁定文件之间是否有任何差异,如果有,它将重新安装在构建目录中,然后执行它在创建映像和容器第一次运行时做了什么。这个tutorial 可以用来和队友分享hook。


注意:请务必使用docker-compose run...,因为docker-compose up... 不允许出现进度指示器。

【讨论】:

【参考方案4】:

首先,有一个操作顺序。构建映像时,不会挂载卷,它们仅在您运行容器时才会挂载。因此,当您完成构建时,所有更改将仅存在于映像中,而不存在于任何卷中。如果您在目录上挂载一个卷,它会覆盖该位置的映像中的任何内容,从而隐藏这些内容(有一个初始化例外,见下文)。


接下来是卷语法:

  volumes:
    - .:/usr/src/app
    - /usr/src/app/node_modules

告诉 docker-compose 从当前目录创建一个主机卷到容器内的/usr/src/app,然后将/usr/src/app/node_modules 映射到由 docker 维护的匿名卷。后者会在docker volume ls中以卷的形式出现,带有一个比较长的uuid字符串,相对没用。

要将/usr/src/app/node_modules 映射到主机上的文件夹,您需要在前面包含文件夹名称和冒号,就像上面一行中的那样。例如。 /host/dir/node_modules:/usr/src/app/node_modules.

命名卷与主机卷有点不同,因为 docker 使用您可以在 docker volume ls 中看到的名称来维护它们。您仅使用名称而不是路径来引用这些卷。所以node_modules:/usr/src/app/node_modules 将创建一个名为node_modules 的卷,您可以将其挂载到仅具有该名称的容器中。

我分开来描述命名卷,因为它们带有一个功能,可以变成带有主机卷的陷阱。 Docker 通过使用该位置的映像内容初始化命名卷来帮助您处理命名卷。所以在上面的例子中,如果命名卷 node_modules 是空的(或新的),它会首先将 /usr/src/app/node_modules 中的镜像内容复制到这个卷中,然后将它挂载到你的容器中。

使用主机卷,您将永远不会看到任何初始化,无论是什么初始化 位置,甚至是一个空目录,都是您在容器中看到的所有内容。无法从该目录位置的映像中获取内容以首先复制到该位置的主机卷。这也意味着容器内部所需的目录权限不会自动继承,您需要手动设置在容器内部工作的主机目录的权限。


最后,还有一个适用于 windows 和 mac 的 docker 的小问题,它们在 VM 内运行,并且您的主机卷已安装到 VM。要将卷挂载到主机上,您必须配置应用程序以将主机中的文件夹共享给 VM,然后将 VM 中的卷挂载到容器中。默认情况下,在 Mac 上,包含 /Users 文件夹,但如果您使用其他目录,例如/Projects 目录,甚至是小写的 /users(unix 和 bsd 区分大小写),您都不会在容器内看到 Mac 中的内容。


了解这些基础知识后,一种可能的解决方案是重新设计您的工作流程,以便将目录内容从复制到主机的图像中获取。首先,您需要将文件复制到图像中的不同位置。然后,您需要在容器启动时将文件从该保存的映像位置复制到卷安装位置。当您执行后者时,您应该注意您违背了拥有卷(持久性)的目的,并且可能需要考虑添加一些逻辑以便在运行副本时更具选择性。首先,将 entrypoint.sh 添加到您的构建中,如下所示:

#!/bin/sh
# copy from the image backup location to the volume mount
cp -a /usr/src/app_backup/node_modules/* /usr/src/app/node_modules/
# this next line runs the docker command
exec "$@"

然后更新您的 Dockerfile 以包含入口点和备份命令:

FROM node:6.3

# Create app directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

# Install app dependencies
COPY package.json /usr/src/app/
RUN npm install -g babel babel-runtime babel-register mocha nodemon
RUN npm install

# Bundle app source
COPY . /usr/src/app
RUN cp -a /usr/src/app/. /usr/src/app_backup

EXPOSE 1234
ENTRYPOINT [ "/usr/src/app/entrypoint.sh" ]
CMD [ "npm", "start" ]

然后从 docker-compose.yml 中删除额外的卷:

  volumes:
    - .:/usr/src/app

【讨论】:

我认为您需要修复卷的安装方式。不仅要减少额外的音量。正如我在答案中发布的那样。 如果他们希望将node_modules 保存在./node_modules 中,则上述工作。否则,是的,他们需要指定不同的卷安装,如您所示。 如果我没记错的话,指定一个卷,它会创建一个匿名卷。它缺少本地(主机)目录。 docker-compose.yml 中的底部卷没有。最上面的卷部分是我从问题中复制的,然后解释说它创建了一个匿名卷。如果有更好的表达方式,请告诉我。 @Tokenyet .:/usr/src/app bind 将当前目录挂载为一个卷。 /usr/src/app/node_modules 创建一个匿名卷。 success.docker.com/article/different-types-of-volumes【参考方案5】:

改变:

  volumes:
    - .:/usr/src/app
    - /usr/src/app/node_modules

收件人:

  volumes:
    - .:/usr/src/app

它会将 node_modules 放入您的本地映射卷中。按照您的方式,/usr/src/app/node_modules 将存储在不同的卷中,您需要 docker inspect container-name 来查找位置。如果您确实想指定位置,请按如下方式指定:

- /path/to/my_node_modules:/usr/src/app/node_modules

【讨论】:

一开始我试过了,但如果我的 docker-copose.yml 卷中没有 - /usr/src/app/node_modules,我的应用程序找不到包。就像他们从未安装过一样。如果我添加它,我的节点 js 文件可以工作,但文件夹保持为空。 你可以为节点应用发布你的 Dockerfile 吗?

以上是关于node_modules 上的 docker-compose 卷但为空的主要内容,如果未能解决你的问题,请参考以下文章

在内存中编译 Webpack 但解析为磁盘上的 node_modules

CentOS 上的 Node.js 镜像

有条件地忽略 COPY 上的 .dockerignore 文件

NodeJS 作为服务结构上的来宾可执行文件

关于管理 React node_modules 的建议

NPM:在node_modules目录中编辑es6编写的插件而不进行转换