在Docker容器中安装node_modules并将其与主机同步

时间:2018-06-29 08:41:49

标签: docker npm docker-compose node-modules

我在Docker容器中安装node_modules并将它们与主机同步时遇到问题。我的Docker的版本为18.03.1-ce, build 9ee9f40,而Docker Compose的版本为1.21.2, build a133471

我的docker-compose.yml如下:

# Frontend Container.
frontend:
  build: ./app/frontend
  volumes:
    - ./app/frontend:/usr/src/app
    - frontend-node-modules:/usr/src/app/node_modules
  ports:
    - 3000:3000
  environment:
    NODE_ENV: ${ENV}
  command: npm start

# Define all the external volumes.
volumes:
  frontend-node-modules: ~

我的Dockerfile

# Set the base image.
FROM node:10

# Create and define the working directory.
RUN mkdir /usr/src/app
WORKDIR /usr/src/app

# Install the application's dependencies.
COPY package.json ./
COPY package-lock.json ./
RUN npm install

许多博客文章和Stack Overflow答案中都描述了与外部卷有关的技巧。例如,this one

该应用程序运行良好。源代码已同步。热装也很好。

我唯一的问题是node_modules文件夹在主机上为空。可以将Docker容器内的node_modules文件夹与主机同步吗?

我已经阅读了以下答案:

  1. docker-compose volume on node_modules but is empty
  2. Accessing node_modules after npm install inside Docker

不幸的是,他们对我没有太大帮助。我不喜欢the first one,因为由于跨平台问题(例如主机是Windows或Mac,而Docker容器是Debian 8),我不想在主机上运行npm install或Ubuntu 16.04)。 The second one对我也不利,因为我想在npm install中运行Dockerfile而不是在启动Docker容器之后运行它。

我还找到了this blog post。作者试图解决我面临的同样问题。问题是node_modules不会同步,因为我们只是将它们从Docker容器复制到主机。

我希望将Docker容器中的node_modules与主机同步。请考虑到我想要的:

  • 自动安装node_modules而不是手动安装
  • node_modules安装在Docker容器而不是主机中
  • 使node_modules与主机同步(如果我在Docker容器中安装了一些新软件包,则应自动与主机同步,而无需任何手动操作)

我需要在主机上放置node_modules,因为:

  • 在需要时可以阅读源代码
  • IDE需要node_modules才能在本地安装,以便它可以访问devDependencieseslint之类的prettier。我不想在全球安装这些devDependencies

谢谢。

11 个答案:

答案 0 :(得分:16)

首先,我要感谢David Mazetrust512发布他们的答案。不幸的是,他们没有帮助我解决我的问题。

我想发表这个问题的答案。

我的docker-compose.yml

---
# Define Docker Compose version.
version: "3"

# Define all the containers.
services:
  # Frontend Container.
  frontend:
    build: ./app/frontend
    volumes:
      - ./app/frontend:/usr/src/app
    ports:
     - 3000:3000
    environment:
      NODE_ENV: development
    command: /usr/src/app/entrypoint.sh

我的Dockerfile

# Set the base image.
FROM node:10

# Create and define the node_modules's cache directory.
RUN mkdir /usr/src/cache
WORKDIR /usr/src/cache

# Install the application's dependencies into the node_modules's cache directory.
COPY package.json ./
COPY package-lock.json ./
RUN npm install

# Create and define the application's working directory.
RUN mkdir /usr/src/app
WORKDIR /usr/src/app

最后但并非最不重要的entrypoint.sh

#!/bin/bash

cp -r /usr/src/cache/node_modules/. /usr/src/app/node_modules/
exec npm start

这里最棘手的部分是将node_modules安装到node_module中定义的/usr/src/cache的缓存目录(Dockerfile)中。之后,entrypoint.shnode_modules从缓存目录(/usr/src/cache)移到我们的应用程序目录(/usr/src/app)。因此,整个node_modules目录将出现在我们的主机上。

看着上面我想要的问题:

  
      
  • 自动安装node_modules而不是手动安装
  •   
  • node_modules安装在Docker容器而不是主机中
  •   
  • 使node_modules与主机同步(如果我在Docker容器中安装了一些新软件包,则应该   自动与主机同步,无需任何手动操作
  •   

第一件事已完成:node_modules已自动安装。第二件事也完成了:node_modules已安装在Docker容器内(因此,不会出现跨平台问题)。第三件事也完成了:安装在Docker容器中的node_modules在我们的主机上将可见,并且将它们同步!如果我们在Docker容器中安装了一些新软件包,它将立即与我们的主机同步。

需要注意的重要事情:确实,安装在Docker容器中的新软件包将出现在/usr/src/app/node_modules中。由于此目录已与我们的主机同步,因此此新软件包也将出现在我们主机的node_modules目录中。但是/usr/src/cache/node_modules在这一点上将具有旧版本(没有此新包)。无论如何,这对我们来说不是问题。在下一个docker-compose up --build(需要--build)期间,Docker将重新安装node_modules(因为package.json已更改),并且entrypoint.sh文件会将它们移至我们的/usr/src/app/node_modules

您应该考虑另一件事。如果您git pull来自远程存储库的代码,或者git checkout your-teammate-branch来自Docker运行时的代码,则可能有一些新软件包添加到package.json文件中。在这种情况下,您应该使用CTRL + C停止Docker,然后使用docker-compose up --build重新启动Docker(需要--build)。如果您的容器作为守护程序运行,则只需执行docker-compose stop即可停止容器,并使用docker-compose up --build重新启动容器(需要--build)。

如果您有任何疑问,请在评论中让我知道。

希望这会有所帮助。

答案 1 :(得分:6)

遇到此问题并发现接受的答案非常慢,无法在每次运行的容器中将所有node_modules复制到主机上,我设法通过在容器中安装依赖项,镜像主机卷和如果存在node_modules文件夹,请再次跳过安装:

Dockerfile:

FROM node:12-alpine

WORKDIR /usr/src/app

CMD [ -d "node_modules" ] && npm run start || npm ci && npm run start

docker-compose.yml:

version: '3.8'

services:
  service-1:
    build: ./
    volumes:
      - ./:/usr/src/app

当您需要重新安装依赖项时,只需删除node_modules

答案 2 :(得分:4)

这里发生了三件事:

  1. 运行docker builddocker-compose build时,您的Dockerfile会构建一个新映像,其中包含一个/usr/src/app/node_modules目录和一个Node安装,但没有其他内容。特别是,您的应用程序不在内置映像中。
  2. docker-compose up时,volumes: ['./app/frontend:/usr/src/app']指令将隐藏/usr/src/app中的所有内容,并将主机系统内容安装在其上。
  3. 然后,volumes: ['frontend-node-modules:/usr/src/app/node_modules']指令将命名卷安装在node_modules树的顶部,隐藏相应的主机系统目录。

如果要启动另一个容器并将其连接到该容器,我希望您会在其中看到node_modules树。对于您要描述的内容,您只是不想要指定的卷:从volumes:块和volumes:文件末尾的docker-compose.yml部分删除第二行。

答案 3 :(得分:2)

我不建议重叠的卷,尽管我还没有看到任何官方文档禁止它,但是过去我对此有一些疑问。我是怎么做的:

  1. 摆脱外部卷,因为您不打算实际使用它,而是打算使用它-停止+删除容器后,使用在容器中专门创建的数据重新生成容器。

可以通过稍微缩短撰写文件来实现以上目的:

frontend:
  build: ./app/frontend
  volumes:
    - ./app/frontend:/usr/src/app
  ports:
    - 3000:3000
  environment:
    NODE_ENV: ${ENV}
  command: npm start
  1. 在不需要时,请避免使用Dockerfile指令重叠卷数据。

这意味着您可能需要两个Dockerfile-一个用于本地开发,一个用于部署胖映像,其中所有应用程序dist文件都分层放置在其中。

也就是说,考虑开发Dockerfile:

FROM node:10
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
RUN npm install

以上内容使应用程序创建完整的node_modules安装并将其映射到您的主机位置,而docker-compose指定的命令将启动您的应用程序。

答案 4 :(得分:2)

没有人提到实际使用docker的entrypoint功能的解决方案。

这是我的工作解决方案:

Dockerfile(多阶段构建,因此它既可以生产也可以本地开发):

FROM node:10.15.3 as production
WORKDIR /app

COPY package*.json ./
RUN npm install && npm install --only=dev

COPY . .

RUN npm run build

EXPOSE 3000

CMD ["npm", "start"]


FROM production as dev

COPY docker/dev-entrypoint.sh /usr/local/bin/

ENTRYPOINT ["dev-entrypoint.sh"]
CMD ["npm", "run", "watch"]

docker / dev-entrypoint.sh:

#!/bin/sh
set -e

npm install && npm install --only=dev ## Note this line, rest is copy+paste from original entrypoint

if [ "${1#-}" != "${1}" ] || [ -z "$(command -v "${1}")" ]; then
  set -- node "$@"
fi

exec "$@"

docker-compose.yml:

version: "3.7"

services:
    web:
        build:
            target: dev
            context: .
        volumes:
            - .:/app:delegated
        ports:
            - "3000:3000"
        restart: always
        environment:
            NODE_ENV: dev

通过这种方法,您可以达到所需的所有3个点,而且恕我直言,这是一种更简洁的方法-无需移动文件。

答案 5 :(得分:1)

感谢Vladyslav Turakentrypoint.sh的回答,我们在此将node_modules从容器复制到主机。

我实现了类似的操作,但是遇到了沙哑的@ commitlint,tslint npm软件包的问题。
我什么都不能推入仓库。
原因:我将node_modules从Linux复制到Windows。在我的情况下,<5%的文件是不同的(.bin和大多数package.json),而95%的文件是相同的。 example: image with diff

因此,我首先返回了npm install中的node_modules的解决方案,用于Windows(用于IDE和调试)。 Docker映像将包含Linux版本的node_modules

答案 6 :(得分:1)

我知道这已解决,但是呢:

Dockerfile:

ArrayList

docker-composer.yml:

FROM node

# Create app directory
WORKDIR /usr/src/app

# Your other staffs

EXPOSE 3000

volumes / app / package.json

version: '3.2'
services:
    api:
        build: ./path/to/folder/with/a/dockerfile
        volumes:
            - "./volumes/app:/usr/src/app"
        command: "npm start"

运行后,node_modules将存在于您的卷中,但是其内容是在容器内生成的,因此不会出现跨平台问题。

答案 7 :(得分:1)

您提到的将主机node_modules文件夹与容器node_modules绑定不是一个好习惯。我已经看到了为此文件夹创建内部卷的解决方案。不这样做会在构建阶段引起问题。

当我尝试为有角度的应用程序构建docker开发环境时遇到了这个问题,当我编辑主机文件夹中的文件时会显示tslib错误,这是因为主机的node_modules文件夹为空(符合预期)。 / p>

在这种情况下,对我有帮助的廉价解决方案是使用名为“ Remote-Containers” 的Visual Studio代码扩展。

此扩展名将使您可以将 Visual Studio代码附加到容器,并透明地编辑容器文件夹中的文件。为此,它将在开发容器内安装内部vscode服务器。有关更多信息,请检查this link

但是,请确保您的卷仍在docker-compose.yml文件中创建。

我希望它对您有帮助:D!

答案 8 :(得分:1)

简单、完整的解决方案

您可以使用外部命名卷技巧在容器中安装 node_modules,并通过将卷的存储位置配置为指向主机的 node_modules 目录来将其与主机同步。这可以通过使用 volumelocal driver 的命名 bind mount 来完成,如下例所示。

无论如何,卷的数据都存储在您的主机上,例如 /var/lib/docker/volumes/,因此我们只是将其存储在您的项目中。

要在 Docker Compose 中执行此操作,只需将您的 node_modules 卷添加到您的前端服务,然后在命名卷部分中配置该卷,其中“设备”是相对路径(来自docker-compose.yml 的位置)到本地(主机)node_modules 目录。

docker-compose.yml

version: '3.9'

services:
  ui:
    # Your service options...
    volumes:
      - node_modules:/path/to/node_modules

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

此解决方案的关键是永远不要直接在主机 node_modules 中进行更改,而是始终在容器中安装、更新或删除 Node 包。

文档:

答案 9 :(得分:0)

我不确定您为什么要让源代码驻留在容器中,而 主机在开发过程中相互绑定安装。通常,您希望源代码驻留在容器中以进行部署,而不是用于开发,因为源代码可在主机上使用并绑定安装。

您的docker-compose.yml

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

您的Dockerfile

FROM node:10

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

当然,每次package.json更改时,您都必须第一次运行npm install,但是您必须在容器中运行它,所以不会出现跨平台问题:docker-compose exec frontend npm install

最后启动服务器docker-compose exec frontend npm start

然后,通常在以部署为目标的CI管道中,您将复制完整的源代码并重新安装node_modules来构建最终映像,但是当然,此时您不再需要绑定安装和“同步” ,因此您的设置应如下所示:

docker-compose.yml

frontend:
  build:
    context: ./app/frontend
    target: dev
  volumes:
    - ./app/frontend:/usr/src/app

Dockerfile

FROM node:10 as dev

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

FROM dev as build

COPY package.json package-lock.json ./
RUN npm install

COPY . ./

CMD ["npm", "start"]

随后,您可以手动或在管道中将Dockerfile的构建阶段作为目标,以构建可部署的映像。

我知道这不是对您问题的确切答案,因为您必须运行npm install并且在开发过程中容器中没有任何内容,但是它解决了您的node_modules问题,并且我觉得您的问题混合了开发和部署方面的考虑,因此也许您以错误的方式想到了这个问题。

答案 10 :(得分:0)

我的解决方法是在容器启动时而不是在构建时安装依赖项。

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