andrhohlov
@andrhohlov
Frontend developer

Как вы используете 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 варианта с установкой модулей на контейнере в другую директорию и копированием их во время запуска.

На мой взгляд, решение с дублированием установки модулей на локальной машине выглядит адекватнее с точки зрения производительности и в целом достаточно вменяемым компромиссом.
Как работает с этим вы?
  • Вопрос задан
  • 1781 просмотр
Решения вопроса 2
@Elluren
Для 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 Автор вопроса
Frontend developer
Решение, протестированное на 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 версия так-то даже не обязательна при создании пакета)
Ответ написан
Ваш ответ на вопрос

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

Войти через центр авторизации
Похожие вопросы
Acme Crypto Corp Нижний Новгород
от 80 000 до 140 000 руб.
Evry Санкт-Петербург
от 2 000 до 2 300 usd.