Большинство из нас уже давно научилось готовить Docker и используют его на локальных машинах, на тестовых стендах и на боевых серверах. Docker, который недавно превратился в Moby, прочно вошел в процессы доставки кода до пользователя. Но best practice работы с контейнерной виртуализацией и, в частности, с Docker вырабатываются до сих пор. В начале становления Docker как основного инструмента изоляции процессов, многие использовали его аналогично использованию виртуальных машин. Подход был максимально прост: устанавливаем все необходимые зависимости в образ (Docker Image), там же билдим всё, что должно билдиться а что не должно двигаем и билдим, получаем артефакт сборки и запекаем всё это в итоговый образ. Такой подход имеет явные недостатки: софт, который нужен для сборки, не всегда нужен для работы, например, для сборки программы на C++ или Go понадобится компилятор, но полученный бинарник можно запускать уже без компилятора. При этом софт, необходимый для сборки, может весить намного больше, чем полученный артефакт.
Второй недостаток вытекает из первого: больше софта в итоговом образе — больше уязвимостей, а значит, мы теряем в безопасности наших сервисов. Сегодня устоявшейся практикой является отделение образа для сборки от образа для запуска. Выглядит и используется это примерно так: Подход с разделением Dockerfile хорошо себя зарекомендовал, но само разделение — это довольно рутинная и не всегда приятная задача, поэтому идеи над упрощением процесса появились довольно давно. Об идее build-stages внутри Dockerfile я услышал от ребят из Grammarly. Они давно реализовали стадии сборки в фасаде над Docker'ом и назвали его Rocker. Но в самом Docker Engine такой функциональности не было. И вот, в Docker наконец смержили пулл-реквест, который реализует стадии сборки (https://github.com/moby/moby/pull/32063), сейчас они доступны в версии v17.05.0-ce-rc2 https://github.com/moby/moby/releases/tag/v17.05.0-ce-rc2 Теперь отдельные Dockerfile'ы для билда перестали быть нужны, так как появилась возможность разделять стадии сборки в одном Dockerfile. В build stage возможно произвести все операции, связанные со сборкой, а уже только артефакт отправлять в следующую стадию, из которой на выходе получим image только с необходимыми для работы сервиса обвесами. Как пример возьмем сервис на Golang. Dockerfile такого сервиса с разделением стадий в общем случае может выглядеть так: Результаты сборки: 330MB на стадии билда, 16.5MB после билда и готовое к запуску. Всё в одном Dockerfile с минимальной конфигурацией. В системе build-стадия сохраняется на диск как <none>:<none>. Возможно использование более двух стадий, например если вы собираете отдельно бекенд и фронтенд. При этом не обязательно наследоваться от предыдущего шага, вполне легально запускать шаг с новым родителем. Если образ родителя не будет найден на машине, то Docker подгрузит его в момент перехода к шагу. Каждая инструкция FROM обнуляет все предыдущие команды. Вот пример того, как можно использовать несколько стадий сборки: Для выбора стадии сборки предлагается использовать флаг --target. С этим флагом сборка осуществляется до указанной стадии. (Включая все предыдущие) На диск в этом случае сохранится и отметится тегом именно эта стадия. Релиз 17.05.0 запланирован на 2017-05-03. И насколько можно судить, это действительно полезный функционал, особенно для компилируемых языков. Спасибо за внимание. Software Engineer