docker-compose在node_modules上的卷但是为空

时间:2016-07-17 21:15:55

标签: node.js docker docker-compose docker-volume

我对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,所有包都在那里。

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

谢谢

5 个答案:

答案 0 :(得分:10)

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生成的npm_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中的一个卷)中挂起,然后麻烦无论你想要什么。
  3. 修改2.(选项2)工作示例,克隆并尝试: https://github.com/xbx/base-server 我在你的这个仓库里自动完成了这一切。

    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" ]
    

    搬运工-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,它对我有用。

答案 1 :(得分:5)

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

接下来是卷语法:

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

告诉docker-compose从当前目录创建一个主机卷到容器内的/usr/src/app,然后将/usr/src/app/node_modules映射到由docker维护的匿名卷。后者将显示为docker volume ls中的一个卷,其长字符串相对无用。

要将/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文件夹,但如果您使用其他目录,例如a / Projects目录,甚至是小写/用户(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

答案 2 :(得分:1)

我在@Robert's answer上加上了一些内容,因为其中有些事情没有考虑在内。即:

  • cp花费的时间太长,用户无法查看进度。
  • 如果node_modules是通过主机安装的,我希望将其覆盖。
  • 如果容器有任何更改,我希望能够git pull处于运行状态并且不运行容器,并相应地更新node_modules
  • 我只希望在开发环境中有这种行为。

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

# 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

无论何时安装软件包,我都会利用postinstall文件的postuninstallpackage.json钩子从工作目录中复制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的环境变量,并将其设置为1文件中的docker-compose.yml。这样,当有人将其安装在容器外部时,它将不会运行。另外,我确保删除了.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脚本将首先检查锁定文件之间是否存在任何差异,如果存在,它将重新安装在build目录中,并执行创建图像和首次运行容器时的操作。这个tutorial可以用来与队友共享钩子。


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

答案 3 :(得分:1)

最简单的解决方案

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

首先,将您的 node_modules 卷添加到您的服务中:

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

然后,在命名卷部分配置卷:

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

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

答案 4 :(得分:0)

变化:

  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