Установите node_modules внутри контейнера Docker и синхронизируйте их с хостом

У меня проблема с установкой node_modules внутри контейнера Docker и синхронизацией их с хостом. Моя версия 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 dependencies.
COPY package.json ./
COPY package-lock.json ./
RUN npm install

Уловка с внешним объемом описана во многих постах блога и ответах Stack Overflow. Например, этот.

Приложение прекрасно работает. Исходный код синхронизирован. Горячая перезагрузка тоже отлично работает.

Единственная проблема, которая у меня есть, это то, что папка node_modules пуста на хосте. Можно ли синхронизировать папку node_modules которая находится внутри контейнера Docker, с хостом?

Я уже прочитал эти ответы:

  1. том docker-compose для node_modules, но пустой
  2. Доступ к node_modules после установки npm в Docker

К сожалению, они мне не очень помогли. Мне не нравится первый, потому что я не хочу запускать npm install на моем хосте из-за возможных межплатформенных проблем (например, хост - Windows или Mac, а контейнер Docker - Debian 8 или Ubuntu 16.04). Второй Dockerfile тоже мне не подходит, потому что я хотел бы запустить npm install в моем Dockerfile вместо того, чтобы запускать его после запуска контейнера Docker.

Кроме того, я нашел этот пост в блоге. Автор пытается решить ту же проблему, с которой я столкнулся. Проблема в том, что node_modules не будут синхронизированы, потому что мы просто копируем их из контейнера Docker на хост.

Я хотел бы, чтобы мои node_modules внутри контейнера Docker были синхронизированы с хостом. Пожалуйста, примите во внимание, что я хочу:

  • установить node_modules автоматически, а не вручную
  • установить node_modules внутри контейнера Docker вместо хоста
  • синхронизировать node_modules с хостом (если я устанавливаю новый пакет внутри контейнера Docker, он должен синхронизироваться с хостом автоматически без каких-либо действий вручную)

Мне нужно иметь node_modules на хосте, потому что:

  • возможность читать исходный код, когда мне нужно
  • интегрированная среда должна node_modules быть установлен локально, чтобы он мог иметь доступ к devDependencies, таких как eslint или prettier. Я не хочу устанавливать эти devDependencies глобально.

Заранее спасибо.

Ответ 1

Сначала я хотел бы поблагодарить Дэвида Мазе и доверие512 за отправку их ответов. К сожалению, они не помогли мне решить мою проблему.

Я хотел бы опубликовать свой ответ на этот вопрос.

Мой 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 cache directory.
RUN mkdir /usr/src/cache
WORKDIR /usr/src/cache

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

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

И последнее, но не менее entrypoint.sh: entrypoint.sh:

#!/bin/bash

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

Самая сложная часть здесь - установить node_modules в node_module кеша node_module (/usr/src/cache), который определен в нашем Dockerfile. После этого entrypoint.sh переместит node_modules из каталога кеша (/usr/src/cache) в наш каталог приложений (/usr/src/app). Благодаря этому весь каталог node_modules появится на нашей главной машине.

Глядя на мой вопрос выше, я хотел:

  • для установки node_modules автоматически вместо ручного
  • установить node_modules внутри контейнера Docker вместо хоста
  • чтобы node_modules синхронизировались с хостом (если я устанавливаю какой-то новый пакет внутри контейнера Docker, он должен быть синхронизирован с хостом автоматически без каких-либо действий вручную

Первое, что делается: node_modules устанавливаются автоматически. Во-вторых, тоже: node_modules устанавливаются внутри контейнера Docker (поэтому проблем с межплатформенностью не будет). И третье дело сделано: node_modules которые были установлены внутри контейнера Docker, будут видны на нашей главной машине, и они будут синхронизированы ! Если мы установим новый пакет внутри контейнера Docker, он будет синхронизирован с нашей машиной сразу.

Важно отметить: действительно, новый пакет, установленный внутри контейнера Docker, появится в /usr/src/app/node_modules. Поскольку этот каталог синхронизирован с нашей машиной, этот новый пакет также появится в нашем узле узла node_modules. Но в этом случае /usr/src/cache/node_modules будет иметь старую сборку (без этого нового пакета). Во всяком случае, это не проблема для нас. Во время следующей docker-compose up --build --build (требуется --build) Docker будет переустанавливать node_modules (потому что package.json был изменен), и файл entrypoint.sh переместит их в наш /usr/src/app/node_modules.

Вы должны учесть еще одну важную вещь. Если вы git pull код из удаленного хранилища или git checkout your-teammate-branch по package.json git checkout your-teammate-branch, когда Docker работает, там могут быть некоторые новые пакеты, добавленные в package.json файл. В этом случае вы должны остановить Docker с помощью CTRL + C а затем снова с помощью docker-compose up --build (--build). Если ваши контейнеры запущены в качестве демона, вы должны просто выполнить docker-compose stop docker-compose up --build чтобы остановить контейнеры, а затем снова с помощью docker-compose up --build (--build).

Если у вас есть какие-либо вопросы, пожалуйста, дайте мне знать в комментариях.

Надеюсь это поможет.

Ответ 2

Здесь три вещи:

  1. Когда вы запускаете docker build или docker-compose build, ваш Dockerfile создает новое изображение, содержащее каталог /usr/src/app/node_modules и установку узла, но ничего больше. В частности, ваше приложение не находится на встроенном изображении.
  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: block и volumes: в конце файла docker-compose.yml.

Ответ 3

Я бы не предлагал дублирование томов, хотя я не видел, чтобы какие-либо официальные документы запрещали его, у меня были некоторые проблемы с ним в прошлом. Как я это делаю:

  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 dist приложения, расположенными внутри.

Тем не менее, рассмотрим разработку Dockerfile:

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

Вышесказанное заставляет приложение создать полную установку node_modules и сопоставить его с местом вашего хоста, в то время как команда, указанная командой docker-compose, запустит ваше приложение.

Ответ 4

Я знаю, что это было разрешено, но как насчет:

Dockerfile:

FROM node

# Create app directory
WORKDIR /usr/src/app

# Your other staffs

EXPOSE 3000

докер-composer.yml:

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

объемы/приложение /package.json

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

После запуска node_modules будут присутствовать в ваших томах, но его содержимое создается внутри контейнера, поэтому проблем с кросс-платформой не возникает.

Ответ 5

Никто не упомянул о решении с использованием функции точки 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"]

Докер /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 "[email protected]"
fi

exec "[email protected]"

докер-compose.yml:

version: "3.7"

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

При таком подходе вы получаете все 3 балла, которые вам необходимы, и, тем не менее, это намного более чистый способ - не нужно перемещать файлы.

Ответ 6

Спасибо Vladyslav Turak за ответ с помощью entrypoint.sh где мы копируем node_modules из контейнера в хост.

Я реализовал аналогичную вещь, но я столкнулся с проблемой с хриплыми, @commitlint, tslint пакетами npm.
Я не могу ничего вставить в репозиторий.
Причина. Я скопировал node_modules из Linux в Windows. В моем случае <5% файлов разные (.bin и большинство package.json), а 95% - то же самое. пример: изображение с diff

Поэтому я вернулся к решению с npm install node_modules для Windows в первую очередь (для IDE и отладки). И изображение Docker будет содержать версию node_modules на node_modules.

Ответ 7

Как вы упоминаете, связывание вашей папки host_modules с вашим контейнером node_modules не является хорошей практикой. Я видел решение создания внутреннего тома для этой папки довольно часто. Невыполнение этого требования вызовет проблемы на этапе строительства.

Я столкнулся с этой проблемой, когда пытался создать среду разработки докеров для angular приложения, которое показывает ошибки tslib при редактировании файлов в папке хоста, поскольку папка host host_modules была пустой (как и ожидалось).

Дешевое решение, которое помогло мне в этом случае, заключалось в использовании расширения кода Visual Studio под названием "Удаленные контейнеры".

Это расширение позволит вам прикрепить код Visual Studio к вашему контейнеру и прозрачно редактировать ваши файлы в папках вашего контейнера. Для этого он установит внутренний сервер vscode в ваш контейнер разработки. Для получения дополнительной информации проверьте эту ссылку.

Однако убедитесь, что ваши тома по-прежнему создаются в файле docker-compose.yml.

Надеюсь, это поможет: D!