docker-在 node_modules 上撰写卷但为空

docker-compose volume on node_modules but is empty

我是 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 Mac。当我 运行 docker-compose up -d 一切正常时,它会在我的计算机上创建一个 node_modules 文件夹,但它是空的。我进入容器的 bash 和 ls node_modules,所有包裹都在那里。

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

谢谢

变化:

  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

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


接下来是卷语法:

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

告诉docker-compose从当前目录创建一个主机卷到容器内的/usr/src/app,然后将/usr/src/app/node_modules映射到由[=52维护的匿名卷=].后者会以卷的形式出现在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` 中的图像内容复制到这个卷,然后将它挂载到你的容器中。

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


最后,docker 对于 windows 和 mac 有一个小问题,它们 运行 在 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 "$@"

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

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

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,您可以:

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

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

Docker 文件

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

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

我在 OSX,它适合我。

我在 上添加了一些内容,因为它没有考虑到一些事情;即:

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

为了解决第一个问题,我在我的图片上安装了rsync,还有pv(因为我想在删除的同时查看进度)。由于我使用的是高山,所以我在 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  direcotry $nm folder size..."
    folderSize=$( du -a /$nm | wc -l )
    echo "Deleting incompatible  directory $nm folder..."
    rm -rfv /$nm/* | pv -lfpes "$folderSize" > /dev/null # Delete all files in node_modules.
    rm -rf /$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 [ "" != "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钩子;即 post-merge 钩子。每次我拉,它会尝试 运行 entrypoint.sh 脚本,如果容器是 运行ning。它还会向脚本 git 提供一个参数,该脚本检查是否 运行 exec yarn:debug,因为容器已经 运行ning。这是我在 .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

如果容器不是 运行ning,并且我获取了更改,那么 entrypoint.sh 脚本将首先检查锁定文件之间是否有任何差异,如果有,它将在构建目录中重新安装,并在第一次创建图像和容器时执行它所做的操作 运行。此 tutorial 可用于与队友共享钩子。


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

最简单的解决方案

使用 Docker Compose 和本地 Volume Driver with a Bind Mount.

配置 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、代码完成、调试等

版本控制提示: 当你的Node package.json/package-lock.json文件发生变化时,无论是拉取还是切换分支,除了重建Image之外,你还必须移除Volume,并删除它的内容:

docker volume rm example_node_modules
rm -rf local/relative/path/to/node_modules
mkdir local/relative/path/to/node_modules