Как вы используете Docker и npm / Composer при локальной разработке (папка node_modules / vendor)?

Задача - ставим только Git, Docker, редактор который нравится. Выполняет `git clone`, `docker-compose up` и приступаем к работе над проектом. Проект - любой набор сервисов и баз данных, как минимум один из который написан на Node.js
Тег Composer потому что у PHP-сообщества похоже такая же проблема.

Dockerfile
FROM node:10-alpine

ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

# Install latest npm
RUN npm i npm@latest -g

WORKDIR /usr/src/app

# Instal dependencies
COPY package*.json ./
RUN npm install

# Copy app code
COPY ./src ./src

# https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user
USER node

CMD ["node", "src/index.js"]

Всё по миллиону best-practices из интернетов. Сначала зависимости чтобы закешировать и каждый раз их не ставить. Затем - исходники.

docker-compose.yaml
version: '3'

services:
    api:
    environment:
      - NODE_ENV=development
    build:
      context: ./api
      args:
        - NODE_ENV=development
    volumes:
      - ./api:/usr/src/app
      - /usr/src/app/node_modules # mount the node_modules directory to the host machine
    ports:
      - 3000:3000
    command: node_modules/.bin/nodemon src/index.js

Переопределяем NODE_ENV, запускаем с помощью nodemon для перезагрузки при изменению кода. И делаем трюк с монтированием папки node_modules без которого она затирается в контейнере.

В итоге всё работает как нужно. Можно даже или заморочиться с правами или просто переопределить юзера в docker-compose.yaml и ставить новые npm-пакеты через `docker-compose exec`.

Но есть одна "маленькая" проблема... На хосте папка node_modules отображается пустой. То есть никаких подсказок в IDE, нет возможности быстро глянуть в код подключаемой либы и т.д.


В итоге я нашёл 2,5 решения проблемы

1. Иметь на машине Node.js и устанавливать модули на машине разработчика. То есть в контейнере у нас модули для работы приложения, а на хосте - для автокомплита в IDE и дебага. Локальная папка с папкой в контейнере не конфликтует. Можно даже иметь опцию запускать проект как в докере так и без него. Работает это быстро, никаких проблем с производительностью и скорее всего у Node.js-разработчика уже есть нода, а условному PHP-разработчику, которому нужно запустить весь проект, но работать только с PHP частью ничего лишнего делать не надо.

2. Устанавливать зависимости не в build-фазе, когда собираем контейнер, а в момент его запуска. Например, это можно определить в скрипте в ENTRYPOINT. А это помимо замедления запуска ещё и синхронизация бездонной папки node_modules между хостом и контейнером. Погуглите "symphony docker performance", там роль node_modules играет папка vendor

2,5. Вариация 2 варианта с установкой модулей на контейнере в другую директорию и копированием их во время запуска.

На мой взгляд, решение с дублированием установки модулей на локальной машине выглядит адекватнее с точки зрения производительности и в целом достаточно вменяемым компромиссом.
Как работает с этим вы?
  • Вопрос задан
  • 5097 просмотров
Решения вопроса 2
@viktorprogger
Для dev-окружения имеет смысл монтировать весь код, включая папку node_modules, как том. И выполнять npm i/composer install в нужный момент.
Та же команда выполняется при сборке образа, в результате все необходимые пакеты оказываются установлены при тестировании и на проде.
Да, такой подход оставляет вероятность расхождения данных между dev и prod версиями. Для моей ситуации эта вероятность достаточно незначительна, чтобы ею пренебречь. В другом же случае стоит воспользоваться вашим вариантом 2.5.
Варианты 1 и 2 не рекомендую по простым причинам:
1. Нарушает принцип единообразия окружения: если у разработчиков, на stage, test и prod окружениях окажутся разные версии nodejs, это может обернуться большими проблемами. Поэтому все операции необходимо производить с помощью программ, установленных в контейнере.
2. Можно напороться на ситуацию, когда удаленные сервера зависимостей не отвечают (перегружены, ddos, упали и пр) или одна из зависимостей с них удалена (я так уже попадал). Другими словами, все содержимое папки node_modules/vendor должно храниться в контейнере. Либо пусть ваш админ поднимает зеркало того же packagist и морочае ся с актуальностью пакетов на нем (в чем смысла мало, если можно хранить все в образе контейнера).
Ответ написан
andrhohlov
@andrhohlov Автор вопроса
Решение, протестированное на MacOS 10.14.3 и Windows 10 Pro.
Есть куда улучшать, например можно добавить multi-stage build и т.д.. Но основная идея думаю останется такой.
Минимальный пример, всё лишнее убрал.


Используем один Dockerfile. Согласно best-practices, для кеширования слоёв, сначала ставим зависимости, затем копируем код, задаём не-рутового пользователя. Можно "выпекать" как есть и в продакшн.

Dockerfile
FROM node:10-alpine

ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

# Install latest npm
RUN npm i npm@latest -g

WORKDIR /usr/src/app

# Instal dependencies
COPY package*.json ./
RUN npm install

# Copy app code
COPY ./src ./src

# https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user
USER node

CMD ["node", "src/index.js"]

В docker-compose.yaml для разработки мы:

1. Задаём root пользователя, чтобы не иметь проблем с правами для установки зависимостей (спорный момент, возможно стоит просто разобраться с правами на директорию)
2. Монтируем папку с кодом - теперь можем видеть изменения без пересборки образа. В этот момент установленные в этой папке внутри контейнера зависимости "исчезнут".
3. Устанавливаем зависимости (теперь они видны и на хосте) и запускаем nodemon для отслеживания изменений. Мне понравился вариант объединить эти две команды, чтобы не выполнять дополнительно docker-compose exec api npm i после старта контейнера. При первом запуске нам в любом случае нужно поставить зависимости, последующие запуски или будут очень быстрыми, или доставят новые зависимости.

docker-compose.yaml
version: '3'

services:
  api:
    environment:
      - NODE_ENV=development
    user: root
    build:
      context: ./api
      args:
        - NODE_ENV=development
    volumes:
      - ./api:/usr/src/app
    ports:
      - 3000:3000
    command: npm i && node_modules/.bin/nodemon src/index.js

А теперь немного магии.
Допустим, я занимаюсь исключительно PHP-частью проекта и мне все эти ваши node_modules на локальной машине не нужны.

Создаём docker-compose.override.yml (должен быть в .gitignore) и переопределяем интересующие нас секции:

docker-compose.override.yaml
version: '3'

services:
  api:
    volumes:
      - ./api:/usr/src/app
      - /usr/src/app/node_modules
    command: node_modules/.bin/nodemon src/index.js

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

Или допустим я работаю на Windows и nodemon в Docker не реагирует на изменения файлов. Лечим:

docker-compose.override.yaml
version: '3'

services:
  api:
    command: node_modules/.bin/nodemon --legacy-watch src/index.js


Подробнее: https://docs.docker.com/compose/extends/

Таким образом, мы получаем возможность быстро запустить проект состоящий из любого количества разных сервисов на разных технологиях, установив только Docker. * Нам доступен автокомлит в IDE, мы можем заглянуть в код подключаемых зависимостей. При этом, мы имеем возможности локальной подстройки проекта - например, пофиксить проблемы нашей операционной системы или не делать видимой папку с зависимостями.

* Конечно, тут могут возникнуть проблемы как с установкой и настройкой самого докера (в том числе поддержкой докера вашей операционной системой), так и работой более сложных конфигов. На мой взгляд, этот путь минимум не сложнее, чем настройка Vagrant'а и явно выигрывает у ручной установки (Python 2, Postgress и ещё чего-то когда твоя зона ответственности только шаблоны и стили, ой, а у тебя же уже стоит Python 3, и кстати по тому старому проекту надо Mongo 2 версии, так что делай что хочешь с установленной 3 версией...и объясни чуваку с WIndows как поставить nvm, чтобы версии Node.js переключать).
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 2
@GrungeDev
Кроме докера, да docker-compose, по сути для дэва ничего и не нужно, ведь внутри контейнера уже есть npm/composer(но это не точно) и можно инсталить прямо из контейнера на смонтированный волюм при помощи "docker-compose run CONTAINER_NAME COMMAND"
Ответ написан
Комментировать
@grinat
Если есть разработчики под windows, то надо иметь в виду что докер несовместим с node.js watch, golang fresh и т.п., потому что не будут работать fs.notify(т.е. nodedemon не будет реагировать на изменения). Поэтому в случае с нодой я кидаю в докер только базы, редисы и т.п. Данные базы лучше кидать в external volumes, если просто в папку, то будут ломаться, особо чувствителен к этому postgre. Заморачиваться со сборкой образа, его загрузкой в регистри есть смысл если много тачек, иначе это будет сплошной гемморой, собирается долго, образы весят дохрена, проще через volumes подключить и в контейнере выполнять composer update, npm i, migrate up, а чтобы версии совпадали давно придуманы lock file'ы, правда в php да, он не спасет, потому что в packagist версия так-то даже не обязательна при создании пакета)
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы