Как работать с Docker: упаковка Spring Boot приложения в контейнер
Немного разбираемся с теорией и проверяем на практике.


vlada_maestro / shutterstock
Эта статья посвящена основам Docker и раскрывает азы работы с контейнерами. Мы изучим базовые определения и самые необходимые команды и даже разработаем и развернём простейшее Java-приложение.
Что такое Docker
Docker — инструмент, предназначенный для быстрой разработки, доставки и развёртывания приложений. Он позволяет упаковать приложение вместе со всеми его зависимостями в так называемый контейнер, а затем запустить его в любой среде.
Идея контейнеризации состоит в том, что на одной машине может разворачиваться множество таких контейнеров с приложениями. Для каждого из них в операционной системе выделяется изолированная область — осуществляется виртуализация на уровне ОС.
Важный момент: все контейнеры запускаются одинаковым способом вне зависимости от того, что находится внутри. Это напоминает контейнеры для морских перевозок — с виду они одинаковы, но внутри могут храниться совершенно разные грузы.
Преимущества контейнеров:
- приложения получают единый механизм сборки;
- не нужно конфигурировать среду для запуска — она поставляется вместе с приложением;
- приложения легче масштабировать;
- есть система оркестрации контейнеров, позволяющая ими управлять.
Основные понятия
Образ — некий шаблон, на основе которого создаются контейнеры. Содержит всё необходимое для запуска приложения. Сюда относятся код, системные утилиты, библиотеки, настройки и так далее. Образ можно представить в виде набора слоёв, которые накладываются друг на друга. Каждый последующий добавляет, изменяет или удаляет файлы предыдущего слоя.
DockerfIle — текстовый файл с набором инструкций по созданию образа, каждая из которых добавляет к образу новый слой.
Контейнер — конкретный экземпляр приложения, созданный на основе образа. Причём из одного образа можно создать сколько угодно контейнеров. Технически контейнер создаётся путём добавления к образу нового слоя, содержащего результаты работы приложения.
Реестр — хранилище образов (как GitHub для кода приложений). Образы можно скачивать из реестра и создавать на их основе контейнеры. Также в реестр можно загружать новые или изменённые образы для дальнейшего использования.
Пример использования
Давайте разработаем простое Spring Boot приложение, создадим на его основе образ и развернём контейнер на локальной машине. Это делается в три простых шага:
- Устанавливаем Docker. Все инструкции можно найти на официальном сайте.
- Генерируем проект с помощью конструктора Spring. Задаём spring-docker-simple в полях Artifact и Name.
- Выбираем одну зависимость — Spring Web. Готово! Скачиваем и распаковываем архив, открываем его в среде разработки.

Код приложения
В открывшемся проекте рядом с главным классом, содержащим метод main, создаём ещё один класс — контроллер с методом hello ().
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloDockerController {
@GetMapping("/")
public String hello() {
return "Hello Docker!";
}
}
Класс помечен аннотацией @RestController, означающей, что он предназначен для обработки web-запросов. А метод помечен аннотацией @GetMapping c адресом «/» — перейдя по нему (выполнив get-запрос к http://localhost:8080/), мы получим сообщение «Hello Docker!»

Теперь открываем терминал и вводим команду:
./mvnw package && java -jar target/spring-docker-simple-0.0.1-SNAPSHOT.jar
Она упакует приложение в jar-файл и запустит его. Чтобы убедиться в корректности работы приложения — перейдём на http://localhost:8080/ в браузере и увидим заветное сообщение.
Теперь создаём файл с именем Dockerfile в корне проекта, который содержит инструкции для сборки образа со следующим текстом:
FROM adoptopenjdk/openjdk11:alpine-jre
ARG JAR_FILE=target/spring-docker-simple-0.0.1-SNAPSHOT.jar
WORKDIR /opt/app
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","app.jar"]

Вот что происходит, когда мы вводим этот код:
Команда | Описание |
---|---|
FROM adoptopenjdk/openjdk11:alpine-jre | Oбраз создаётся на основе alpine linux с установленной openjdk11 |
ARG JAR_FILE=target/spring-docker-simple-0.0.1-SNAPSHOT.jar | Переменной JAR_FILE присваивается путь к jar- архиву |
WORKDIR /opt/app | Назначаем рабочую директорию, в которой будут выполняться дальнейшие команды (перемещаемся в папку app) |
COPY ${JAR_FILE} app.jar | Наш jar-файл, указанный в JAR_FILE, копируется в папку app, и копии задаётся имя app.jar |
ENTRYPOINT [“java","-jar","app.jar"] | jar-файл запускается, собирается команда java -jar app.jar из заданной рабочей директории |
После этого в терминале вводим команду, с помощью которой собираем образ и запускаем контейнер.
docker build -t spring-docker-simple:0.0.1 .
Точка в конце важна, она указывает на расположение Dockerfile (символ «точка» означает текущую директорию. Проверьте, что образ создан командой docker images. Вывод должен быть таким:

Запускаем контейнер командой:
docker run -d -p 8080:8080 -t spring-docker-simple:0.0.1
Опция -d означает старт процесса в фоновом режиме. Опция -p тоже важна — дело в том, что контейнер собирается в полностью изолированном окружении. Тот факт, что приложение внутри контейнера запущено на порту 8080, не означает, что оно доступно вне контейнера на этом порту.
Требуется явно указать, что порту 8080 в контейнере (здесь второе значение — это порт, на котором работает наше приложение в контейнере) соответствует порт 8080 на локальной машине, который будет использоваться при обращении к контейнеру. Поэтому пишем через двоеточие -p 8080:8080.
Теперь введём в терминале команду:
curl http://localhost:8080
Так проверяется работоспособность запущенного контейнера. Есть и альтернативный вариант: можно просто перейти по этому адресу в браузере. Если всё работает как надо, задача выполнена — нам удалось упаковать Spring Boot приложение в контейнер.
Частые команды при работе с Docker
docker ps — выводит список запущенных контейнеров. Также ей можно передать параметр -a, чтобы вывести все контейнеры, а не только запущенные.

docker build — собирает образ Docker из Dockerfile и набора файлов, расположенных по определённому пути.

Параметр -t используется, чтобы задать имя образа, последний параметр. — наименование каталога (в нашем случае текущий каталог).
docker images — выводит список образов в вашей системе.

docker logs — позволяет вывести на консоль логи указанного контейнера. Для этого необходимо указать имя или id контейнера. Можно использовать флаг --follow, чтобы следить за логами работающего контейнера: например, docker logs --follow c5ecc88de8f9.

docker run — запускает контейнер на основе указанного образа.

docker stop — останавливает контейнер. Можно передать опцию $(docker ps -a -q) для остановки всех запущенных контейнеров.

docker rm и docker rmi — команды, удаляющие контейнер и образ соответственно.
Удалить все контейнеры:

Заключение
В этом руководстве мы попробовали самостоятельно работать с Docker. Для этого создали простой Spring проект с одной конечной точкой REST и собрали для него образ Docker. В результате научились запускать образ Docker внутри контейнера и протестировали конечную точку REST внутри этого образа.
Docker — удобный инструмент, который значительно ускоряет рабочий процесс. Чтобы узнать о нём больше и получить наиболее полное представление, рекомендую читать официальную документацию.