Допустим, у нас есть Node.js приложение, которое по каким-то причинам нужно перенести в Docker контейнер и запустить. Может, просто хочется проверить, как оно поведет себя на ‘чистой’ машине. Или выпендриться перед заказчиком. Причины ведь разные бывают.
Для определенности, пускай это будет вэб сервер hello.js, который всем отвечает «Hello World». Такой вот он приветливый.
1 2 3 4 5 6 7 8 9 |
var http = require('http'); http.createServer(function (_, response) { response.writeHead(200, { "Content-Type": "text/plain" }); response.end("Hello World\n"); }) .listen(8080); |
Итак, как перенести его в Docker контейнер и запустить?
Дисклаймер: всё написанное ниже предполагает базовое понимание того, что такое Docker, и зачем он может кому-то пригодиться. Где-то даже была статья про это.
Способ первый: подключить папку с исходным кодом напрямую к файловой системе контейнера.
В Docker клиенте есть параметр -v , при помощи которого можно подключать файлы и папки файловой системы хоста к файловой системе контейнера. То есть если взять контейнер с предустановленным Node.js (в Docker репозитории есть такой — node) и подключить к нему папку проекта — то задача, вроде как, решена. Будем смотреть:
1 2 3 4 5 6 7 8 9 |
docker run \ #run container -p8080:8080 \ #expose its 8080th port -v /Users/pav/helloapp:/helloapp \ #mount ~/helloapp # to /helloapp node \ #image with # preinstalled node.js node /helloapp/hello.js #start hello.js |
Так как на Mac и Windows Docker работает из виртуальной машины, увидеть hello world по http://127.0.0.1:8080 больше не получится — у VM ведь свой IP. Но его можно получить через docker-machine ip . Запускаем, и мне он выдаёт 192.168.99.100. Барабанная дробь…
Теперь оно работает из контейнера!
Это точно не тот способ, который я бы использовал в продакшен, но для мелких задач, или просто, чтобы быстро что-то проверить — самое то. Из минусов, получившийся контейнер не самодостаточен: я не могу его перенести на другую машину без того, чтобы отдельно не скопировать и файлы проекта.
Второй способ может это исправить.
Способ второй: скопировать файлы проекта внутрь контейнера.
Докеровская команда cp создана именно для того, чтобы копировать файлы между хостом и контейнером. Это настолько близко к решению задачи, что я решил немного усложнить процесс, чтобы разнообразить рутину парой новых трюков по дороге. Итак:
1. Запускаем node контейнер в интерактивном режиме:
docker run -ti -p8080:8080 node bash
-ti , как всегда, расшифровывается как tty-interactive.
2. Выходим из контейнера, но оставляем его работать в фоновом режиме: Ctrl+p + Ctrl+q
3. Находим ID контейнера через docker ps :
В моём случае это db8ce50cfd72
4. Копируем файлы проекта в контейнер
1 2 3 4 5 6 |
docker cp \ #copy files /Users/pav/helloapp \ #from local machine db8:/helloapp #to /helloapp of container # who's ID starts with db8 |
В Docker не обязательно указывать ID полностью. Обычно первых нескольких символов достаточно, при условии, конечно, что никакая другая айдишка с них не начинается.
5. Идём назад в контейнер: docker attach db8
6. Запускаем Node.js: node /helloapp/hello.js
И hello world снова работает!
В отличие от первого способа, этот контейнер самодостаточен. Его можно сохранить через docker commit и перенести на другой хост. Правда, если «Hello World» поменяется на «Goodbye cruel world», то эти 6 шагов придётся повторить снова.
Чтобы этого снова не пришлось делать, есть третий способ.
Способ третий: собрать новый образ с проектными файлами через Dockerfile.
Dockerfile позволяет описать структуру образа в обычном текстовом формате и потом банально его ‘скомпилировать’. Если задуматься, то образ, который мы хотим получить, делается всего за четыре шага:
1. Взять существующий node образ,
2. скопировать в него проектные файлы,
3. открыть порт 8080,
4. запустить приложение.
В Dockerfile эти шаги выглядели бы вот так:
1 2 3 4 |
FROM node:latest COPY hello.js /helloapp/hello.js EXPOSE 8080 ENTRYPOINT ["node", "/helloapp/hello.js"] |
Это практически полное совпадение: берем самый свежий (latest) node образ, копируем файлы, открываем порт 8080 и устанавливаем точку входа: как только контейнер запустился — запускаем и hello.js. Кроме команд FROM, COPY, EXPOSE и ENTRYPOINT есть много-много других.
После этого собираем Dockerfile в образ:
docker build -t helloapp:latest .
и получаем на выходе образ с названием helloapp, помеченный как latest (но можно было указать любую другую версию), в котором намертво зашиты проектные файлы. Его можно запускать ( docker run -d helloapp ), переносить между хостами, удалять и пересобирать заново. Dockerfile выступает как исходный код, и его можно положить в git к остальным исходникам.
Мораль.
Из всех трёх подходов первые два хороши для мелких и одноразовых задач, но Dockerfile — то, что нужно, когда новые образы приходится собирать регулярно. Он отлично дружит с git/mercurial/svn, автоматизирует нудный процесс создания образов, и если какие-то его зависимости изменились — пересобрать образ не требует никаких усилий.