Всем привет! В данной статье хочу показать простой пример написания Dockerfile, объяснить как это все работает, а также показать на примере использование многоэтапной сборки. Для понимания данной статьи необходимы минимальные знания Docker, а также для использования примеров - установленный Docker Desktop локально на компьютере. Вначале немного теории. Dockerfile - это файл, который содержит инструкции для сборки образа. На основании образа создается и запускается контейнер. Обратимся к официальной документации https://docs.docker.com/get-started/overview/  Это скриншот взят с официальной страницы.  На этом скриншоте мы видим схему как создаются и запускаются контейнеры. Мы как клиенты через команды docker обращаемся к Docker daemon, который берет локальный образ (image) и на основании его запускает контейнер, если образа локально нет - то он идет в registry (это может быть docker hub) и вначале стягивает его себе на компьютер. И может кто-то спросит "А где тут во всем этим dockerfile?". Именно на основании dockerfile и создается первоначально образ. Для тестирования написания dockerfile создадим простой проект, который по сути ничего особо делать не будет, просто будет приветствовать нас. Зайдем на https://start.spring.io/ и создадим проект назовем его simple-dockerfile-example. Из зависимостей подключим только Spring Web. Генерируем и открываем этот проект в IDEA. Создаем класс ClientService: и класс ClientController: Также в application.properties пропишем одно свойство (client), то есть имя того кого будем приветствовать. Можем протестировать наш код, запустить проект и через postman отправить get запрос на http://localhost:8080/api/v1/client   Должны получить приветствие. Сейчас попробуем на основании нашего проекта сделать образ и его запустить в контейнере. Для этого в корне нашего проекта создадим файл Dockerfile: и в данном файле пишем: Разберем что тут происходит в данном файле. В первой строке мы указываем какой образ нужно стянуть с docker hub. Этот образ нужен для того чтобы в контейнере была развернута своя JDK для запуска нашего проекта. ARG JAR_FILE=target/*.jar - здесь мы создаем просто локальную переменную, которая ссылается на jar-ник нашего проекта. COPY ${JAR_FILE} app.jar - мы копируем наш jar-ник в образ и называем его app.jar. EXPOSE 8080 - указываем на каком внешнем порту будет доступен наш контейнер. ENTRYPOINT ["java", "-jar", "/app.jar"] - мы запускаем наш  jar-ник. Если кому-то не очень понятно что происходит в последней строчке, то это то же самое, что если бы мы предварительно запустили команду package и получили simple-dockerfile-example-0.0.1-SNAPSHOT.jar в папке target. А затем с помощью командной строки зашли в папку target и выполним команду java -jar simple-dockerfile-example-0.0.1-SNAPSHOT.jar И мы таким образом запустили наш проект. Можем сейчас зайти в postman и отправить get запрос на http://localhost:8080/api/v1/client  и получить снова Hello!: Ivan.  Останавливаем проект. Заходим в терминале в корень нашего проекта, где лежит Dockerfile и сейчас будем строить наш образ. Выполним команду docker build . Docker daemon должен стянуть всё необходимое с docker hub. Наш образ готов.  Посмотреть наши образы можно через команду docker images в командной строке или использовать Docker Desktop. Находим его во вкладке Images: Мы видим, что образ есть и он занимает 490,84 МВ. Также выполним команду docker images в командной строке. Дальше давайте запустим наш образ, то есть поместим его в контейнер. Выполним команду docker run -p 8080:8080 b472 -p в данной команде мы указываем порты. Первый порт -это порт на нашем компьютере то есть наш localhost, а второй порт - это порт внутри контейнера.  b472 - это первые 4 буквы ID нашего контейнера, у Вас они будут отличаться. После выполнения данной команды контейнер должен запуститься с нашим проектом и отправив через postman get запрос на http://localhost:8080/api/v1/client  мы снова должны получить Hello!: Ivan. Таким образом мы создали проект, создали образ с данным проектом и развернули его через docker в контейнере. Что плохо написано в нашем Dockerfile. Мы не указали версию JDK (это первая строка FROM eclipse-temurin), если версия не указана, то будет скачана самая последняя доступная версия, что может привести к несовместимости нашего приложения с JDK. И во-вторых здесь мы не компилируем jar, а только его запускаем, поэтому нам бы хватило и JRE и это уменьшит размер нашего образа. Переделаем это. Остановим наш контейнер через Docker Desktop и запустим снова команду  docker build. Выполним команду docker images: И мы увидим, что размер нового образа 188 МВ в 2,5 раза меньше, чем предыдущий, а работает все также. Перейдем сейчас к использованию многоэтапной сборки. Суть ее в том, что как вначале чтобы сделать образ мы сами вручную запускали команду мавена и создавали jar-ник нашего проекта, а при многоэтапной сборке jar-ник будет генерироваться автоматически. Перепишем dockerfile. Разберемся что тут происходит.  FROM eclipse-temurin:17-jdk-alpine as builder - здесь, как мы уже знаем, указываем docker-у какой образ мы будем использовать для сборки нашего проекта и тут мы уже указываем именно jdk, так как нам понадобятся инструменты для компиляции нашего проекта. as builder - это название, которое мы присвоили, для того чтобы обратиться с другого слоя контейнера для получения данных. WORKDIR /opt/app - создаем папки в данном слое COPY .mvn/ .mvn - копируем папку mvn и все ее содержимое в такую же папку в корень данного слоя, для того чтобы у нас был maven. COPY mvnw pom.xml ./ - копируем mvnw и pom.xml тоже в корень данного слоя. RUN ./mvnw dependency:go-offline - данной строчкой мы подтягиваем все зависимости из pom.xml в наш слой, чтобы у нас в контейнере были все зависимости, необходимые для нашего проекта. COPY ./src ./src - копируем непосредственно папку с нашим проектом. RUN ./mvnw clean install - запускаем мавен, который все чистит и создает  jar-ник нашего проекта. строчка 10 WORKDIR /opt/app - создаем папки в другом слое. COPY --from=builder /opt/app/target/*.jar /opt/app/*.jar - используя доступ к первому слою копируем jar-ник с нашим проектом в данный слой. Давайте сейчас запустим все это дело. Вначале остановим и удалим предыдущий запущенный контейнер через Docker Desktop. Запустим снова команду docker build .  Немного подождем пока maven подтянет все зависимости. Запустим команду docker images и посмотрим, что у нас появился новый образ. и запустим его командой docker run -p 8080:8080 86ed Контейнер запустился тестируем. Еще хочу показать одну вещь. На основании одного образа можно запустить несколько контейнеров на разных портах и с разными свойствами. Открываем например еще одну командную строку, я буду использовать PowerShell. Заходим в наш каталог, где лежит dockerfile и выполняем команду: docker run -p 7070:8080 -e client=test 86ed. Здесь мы запускаем контейнер с доступом на порту 7070 и с переменной client=test. Заходим в postman. И мы видим что мы изменили переменную с Ivan на test. Запустим еще один контейнер с параметром develop на порту 9090. Тестим. Заходим в Docker Desktop и можем посмотреть, что все наши контейнеры работают, но работают на разных портах и с разными параметрами. Это нужно для того, чтобы, например, развернуть один и тот же проект, но один для тестирования с подключенной тестовой базой данных, а один с рабочей. Спасибо Всем кто дочитал до конца.