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

Install node_modules inside Docker container and synchronize them with host

我在 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 文件夹与主机同步?

我已经阅读了这些答案:

不幸的是,他们对我帮助不大。我不喜欢 , because I don't want to run npm install on my host because of the possible cross-platform issues (e.g. the host is Windows or Mac and the Docker container is Debian 8 or Ubuntu 16.04). 对我也不好,因为我想 运行 npm install 在我的 Dockerfile 而不是 运行 之后Docker 容器已启动。

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

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

我需要在主机上安装 node_modules,因为:

提前致谢。

这里发生了三件事:

  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: 块中的第二行和 docker-compose.yml 文件末尾的 volumes: 部分。

我不建议重叠卷,虽然我没有看到任何官方文档禁止它,但我过去遇到过一些问题。我是怎么做的:

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

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

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

这意味着您可能需要两个 Dockerfiles - 一个用于本地开发,一个用于部署包含所有应用程序 dist 文件的胖映像。

也就是说,考虑开发 Dockerfile:

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

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

首先,我要感谢David Maze and trust512 post他们的回答。不幸的是,他们没有帮助我解决我的问题。

我想post回答这个问题。

我的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 安装到我们 Dockerfile 中定义的 node_module 的缓存目录 (/usr/src/cache) 中。之后,entrypoint.sh 会将 node_modules 从缓存目录 (/usr/src/cache) 移动到我们的应用程序目录 (/usr/src/app)。多亏了这个,整个 node_modules 目录将出现在我们的主机上。

看我上面的问题我想:

  • to install node_modules automatically instead of manually
  • to install node_modules inside the Docker container instead of the host
  • to have node_modules synchronized with the host (if I install some new package inside the Docker container, it should be synchronized with the host automatically without any manual actions

第一件事是:node_modules 是自动安装的。第二件事也完成了:node_modules 安装在 Docker 容器内(因此,不会有跨平台问题)。第三件事也完成了:安装在 Docker 容器内的 node_modules 将在我们的主机上 visible 并且它们将是 同步!如果我们在 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 再次启动它(需要 --build)。如果您的容器是 运行 作为守护进程,您应该只执行 docker-compose stop 来停止容器并使用 docker-compose up --build 再次启动它(需要 --build)。

如有任何疑问,请在评论中告诉我。

希望对您有所帮助。

感谢 Vladyslav Turak 回答 entrypoint.sh 我们将 node_modules 从容器复制到主机。

我实现了类似的东西,但我 运行 进入了 husky、@commitlint、tslint npm 包的问题。
我无法将任何内容推送到存储库。
原因:我从Linux复制了node_modules到Windows。在我的例子中,<5% 的文件是不同的(.bin 和大多数 package.json),95% 是相同的。 example: image with diff

所以我首先返回解决方案 node_modulesnpm install 用于 Windows(用于 IDE 和调试)。 Docker 图像将包含 Linux 版本的 node_modules

我知道这已经解决了,但是:

Docker 文件:

FROM node

# Create app directory
WORKDIR /usr/src/app

# Your other staffs

EXPOSE 3000

docker-composer.yml:

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

volumes/app/package.json

{
    ... ,
    "scripts": {
        "start": "npm install && node server.js"
    },
    "dependencies": {
        ....
    }
 }

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

没有人提到实际使用 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#-}" != "" ] || [ -z "$(command -v "")" ]; 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 点,恕我直言,这是一种更简洁的方法 - 不需要四处移动文件。

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

我 运行 在尝试为 angular 应用程序构建 docker 开发环境时遇到了这个问题,当我在主机中编辑文件时显示 tslib 错误文件夹导致我的主机的 node_modules 文件夹是空的(如预期的那样)。

在这种情况下,对我有帮助的廉价解决方案是使用名为 "Remote-Containers".

的 Visual Studio 代码扩展

此扩展程序将允许您将 Visual Studio 代码 附加到您的容器,并在您的容器文件夹中编辑 运行 清晰的文件。为此,它将在您的开发容器中安装一个内部 vscode 服务器。有关更多信息,请查看 this link.

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

希望对您有所帮助:D!

运行 解决了这个问题,发现接受的答案很慢,无法将所有 node_modules 复制到每个容器 运行 中的主机,我设法通过安装依赖项解决了这个问题在容器中,镜像主机卷,如果存在 node_modules 文件夹,则跳过再次安装:

Docker 文件:

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.

我不确定为什么你希望你的源代码存在于容器中 and host and bind mount other在开发过程中。通常,您希望您的源代码位于用于部署的容器内,而不是用于开发,因为代码在您的主机上可用并绑定安装。

你的docker-compose.yml

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

你的Dockerfile

FROM node:10

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

当然你必须 运行 npm install 第一次和每次 package.json 改变,但是你 运行 它在容器里所以没有 cross-platform问题: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 的构建阶段为目标,以构建您的 deployment-ready 映像。

我知道这不是您问题的确切答案,因为您必须 运行 npm install 并且在开发过程中容器内没有任何东西,但它解决了您的 node_modules 问题,我觉得您的问题混合了开发和部署注意事项,所以您可能以错误的方式考虑了这个问题。

我的解决方法是在容器启动时而不是在 build-time 期间安装依赖项。

Docker 文件:

# 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

一个简单、完整的解决方案

您可以使用外部命名卷技巧 在容器中安装 node_modules 通过将卷的存储位置配置为指向主机的 将其与主机同步=54=]目录。这可以使用命名的 volume using the local driver and a 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 包。

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

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

文档:

最适合发展

docker-compose.yml

...
  frontend:
    build: ./app/frontend
    ports:
      - 3000:3000
    volumes:
      - ./app/frontend:/usr/src/app
...

./app/frontend/Dockerfile

FROM node:lts

WORKDIR /usr/src/app

RUN npm install -g react-scripts

RUN chown -Rh node:node /usr/src/app

USER node

EXPOSE 3000

CMD [ "sh", "-c", "npm install && npm run start" ]

#FOR PROD
# CMD [ "sh", "-c", "npm install && npm run build" ]

用户node将帮助您获得主人<->客人

的权利

文件夹 node_modules 将可从主机访问并同步主机<->来宾

您也可以使用 dockerized npm install。这与 npm install 相同,但它在 docker 容器上运行。

node_modules 将写入主机。它应该开箱即用,您可以指定要使用的 npm 版本。如果需要,可以扩展或自定义容器。

请注意,某些 npm 包可能需要编译,并且生成的二进制文件可能与您的主机不兼容。如果您只需要源代码或 dist 文件,这不是问题。

免责声明:我是 Dockerized 的作者。