Эта статья является частью нашего курса Академии под названием Docker Tutorial для разработчиков Java .
В этом курсе мы предлагаем серию руководств, чтобы вы могли разрабатывать свои собственные приложения на основе Docker. Мы охватываем широкий спектр тем, от Docker через командную строку, до разработки, тестирования, развертывания и непрерывной интеграции. С нашими простыми учебными пособиями вы сможете запустить и запустить собственные проекты за минимальное время. Проверьте это здесь !
Содержание
1. Введение
В течение первых нескольких частей руководства мы рассмотрели основы Docker и множество способов взаимодействия с ним. Настало время применить полученные знания в реальных проектах Java, начав обсуждение с темы о том, как Docker влияет на хорошо отлаженные процессы и практики сборки.
Честно говоря, цель этого раздела двоякая. Сначала мы рассмотрим, как существующие инструменты сборки, а именно Apache Maven и Gradle , помогают упаковывать приложения Java в контейнеры Docker . Во-вторых, мы будем продвигать эту идею еще дальше и узнаем, как мы могли бы использовать Docker для полной инкапсуляции конвейера сборки наших Java-приложений и создания окончательных образов Docker в конце.
2. Под увеличительным стеклом
Чтобы поэкспериментировать, мы собираемся разработать два простых (но, тем не менее, значимых) веб-приложения на Java, которые бы реализовывали и представляли API REST (ful) для управления задачами.
 Первое приложение будет разработано поверх Spring Boot и Spring Webflux с использованием Gradle в качестве инструмента управления сборкой и зависимостями.  Что касается версий, мы будем использовать последнюю 2.0.0.M6 Spring Boot 2.0.0.M6 , последнюю 2.0.0.M6 Spring Webflux 5.0.1 и последнюю версию Gradle 4.3 . 
  Второе приложение, хотя и функционально эквивалентное первому, будет разработано поверх другой популярной платформы Java, Dropwizard , на этот раз с использованием Apache Maven для управления сборкой и зависимостями.  Что касается версий, мы собираемся представить последний выпуск 1.2.0 Dropwizard и последний выпуск 3.5.2 Apache Maven . 
Как мы уже упоминали, оба приложения будут реализовывать и предоставлять API-интерфейсы REST (ful) для управления задачами, по сути, заключая в себе операции CRUD (создание, чтение, обновление и удаление).
| 
 1 
2 
3 
4 
 | 
GET     /tasksPOST    /tasksDELETE  /tasks/{id}GET     /tasks/{id} | 
Сама задача моделируется как постоянный объект, который будет управляться Hibernate ORM и храниться в базе данных отношений MySQL .
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
 | 
@Entity@Table(name = "tasks")public class Task {    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    @Column(name = "id")    private Integer id;         @Column(name = "title", nullable = false, length = 255)    private String title;         @Column(name = "description", nullable = true, columnDefinition = "TEXT")    private String description;         // Getters and setters are omitted    ...} | 
На этом сходство обоих приложений заканчивается, и каждое из них будет следовать своему идиоматическому пути развития.
3. Градл и Докер
  Итак, все готово, давайте начнем путешествие с изучения того, что нужно для интеграции Docker в типичную сборку Gradle .  Для этого подраздела вам понадобится установить Gradle 4.3 на ваш компьютер для разработки.  Если у вас его еще нет, следуйте инструкциям по установке , выбрав любой предложенный метод, который вы предпочитаете. 
  Чтобы упаковать типичное приложение Spring Boot как образ Docker с помощью Gradle, нам просто нужно включить два дополнительных плагина в файл build.gradle : 
- Плагин Spring Boot Gradle
 - Плагин Palantir Docker Gradle (но есть довольно много альтернативных вариантов )
 
  Конвейер сборки в основном полагался бы на плагин Spring Boot Gradle для создания uber-jar (термин, который часто используется для описания техники создания единственного исполняемого JAR-архива приложения), который позже будет использоваться Palantir Docker Gradle для сборки образа Docker .  Вот как выглядит определение сборки, файл build.gradle . 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
 | 
buildscript {    repositories {    }       dependencies {        classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.0.M6"    }}plugins {    id 'com.palantir.docker' version '0.13.0'}apply plugin: "org.springframework.boot"apply plugin: 'java'apply plugin: 'eclipse'apply plugin: 'application'sourceCompatibility = 1.8targetCompatibility = 1.8dependencies {    compile("org.flywaydb:flyway-core:4.2.0")    compile("org.springframework.boot:spring-boot-starter-webflux:2.0.0.M6")    compile("org.springframework.boot:spring-boot-starter-data-jpa:2.0.0.M6")    compile("org.springframework.boot:spring-boot-starter-actuator:2.0.0.M6")    compile("mysql:mysql-connector-java:8.0.7-dmr")}repositories {    maven {        mavenCentral()    }}springBoot {    mainClassName = "com.javacodegeeks.spring.AppStarter"}jar {    mainClassName = "com.javacodegeeks.spring.AppStarter"    baseName = 'spring-boot-webapp '    version = project.version}bootJar {    baseName = 'spring-boot-webapp '    version = project.version}docker {    name "jcg/spring-boot-webapp:$project.version"    tags 'latest'    dependsOn build    files bootJar    dockerfile file('src/main/docker/Dockerfile')    buildArgs([BUILD_VERSION: project.version])} | 
  Это на самом деле довольно просто, все мясо в основном находится в разделе build.gradle файла build.gradle .  Вы также можете заметить, что мы используем наш собственный Dockerfile , src/main/docker/Dockerfile , чтобы предоставить Docker инструкции по созданию образа. 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
 | 
FROM openjdk:8-jdk-alpineARG BUILD_VERSIONENV DB_HOST localhostENV DB_PORT 3306ADD spring-boot-webapp-${BUILD_VERSION}.jar spring-boot-webapp.jarEXPOSE 19900ENTRYPOINT exec java $JAVA_OPTS -Ddb.host=$DB_HOST -Ddb.port=$DB_PORT -jar /spring-boot-webapp.jar | 
  Действительно, так просто, как только можно.  Обратите внимание, как мы используем инструкцию ARG (и настройку build.gradle файле build.gradle ) для передачи аргументов в изображение.  В этом случае мы передаем версию проекта, чтобы найти окончательные артефакты сборки.  Еще одна интересная деталь, на которую стоит обратить внимание, — это использование инструкций ENV для соединения хоста и порта экземпляра MySQL для подключения.  И, как вы уже догадались, инструкция EXPOSE информирует Docker о том, что контейнер прослушивает порт 19900 во время выполнения. 
Круто, а что дальше? Ну, нам просто нужно запустить нашу сборку Gradle , вот так:
| 
 1 
2 
3 
4 
 | 
> gradle clean docker dockerTag...BUILD SUCCESSFUL in 12s15 actionable tasks: 14 executed, 1 up-to-date | 
  Задача dockerTag самом деле не нужна, но из-за этой проблемы, о которой сообщалось в отношении плагина Palantir Docker Gradle, мы должны явно вызвать его, чтобы правильно dockerTag наше изображение.  Давайте проверим, есть ли у нас изображение, доступное локально. 
| 
 1 
2 
3 
4 
5 
 | 
> docker image lsREPOSITORY               TAG            IMAGE ID      CREATED             SIZEjcg/spring-boot-webapp   0.0.1-SNAPSHOT 65057c7ae9ba  21 seconds ago      133MBjcg/spring-boot-webapp   latest         65057c7ae9ba  21 seconds ago      133MB... | 
Хорошо, новый образ есть прямо из духовки. Мы можем запустить его немедленно, используя инструмент командной строки docker , но сначала нам нужно, чтобы где-то был доступен контейнер MySQL . К счастью, мы делали это уже столько раз, что это нас не озадачит.
| 
 1 
2 
3 
4 
5 
6 
 | 
docker run --rm -d \  --name mysql \  -e MYSQL_ROOT_PASSWORD='p$ssw0rd' \  -e MYSQL_DATABASE=my_app_db \  -e MYSQL_ROOT_HOST=% \  mysql:8.0.2 | 
  Теперь мы готовы запустить наше приложение в виде контейнера Docker .  Есть несколько способов, которыми мы могли бы ссылаться на контейнер MySQL , при этом определяемая пользователем сеть является предпочтительным вариантом.  Для простых случаев, подобных нашему, мы можем просто обратиться к нему, назначив DB_HOST среды DB_HOST IP-адрес работающего контейнера MySQL , например: 
| 
 1 
2 
3 
4 
5 
 | 
docker run -d --rm \  --name spring-boot-webapp \  -p 19900:19900 \  -e DB_HOST=`docker inspect --format '{{ .NetworkSettings.IPAddress }}' mysql` \  jcg/spring-boot-webapp:0.0.1-SNAPSHOT | 
  Сопоставив порт 19900 от контейнера к хосту, мы могли общаться с нашим приложением, получая доступ к его REST (ful) API из curl, используя localhost качестве имени хоста.  Давайте сделаем это прямо сейчас. 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
 | 
$ curl -X POST http://localhost:19900/tasks \   -d '[{"title": "Task #1", "description": "Sample Task"}]' \   -H "Content-Type: application/json"[  {    "id":1,    "title":"Task #1",    "description":"Sample Task"  }] | 
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
 | 
$ curl http://localhost:19900/tasks[  {    "id":1,    "title":"Task #1",    "description":"Sample Task"  }] | 
| 
 1 
2 
3 
4 
5 
6 
7 
 | 
$ curl http://localhost:19900/tasks/1{  "id":1,  "title":"Task #1",  "description":"Sample Task"} | 
Под капотом много движущихся частей, например, автоматическая миграция базы данных с использованием Flyway и встроенная поддержка проверки работоспособности с помощью Spring Boot Actuator . Некоторые из них появятся в следующих разделах руководства, но посмотрите, насколько просто и естественно создать и упаковать свои приложения Spring Boot в виде образов Docker с помощью Gradle .
4. Gradle на докере
Оказывается, что создание образов Docker с помощью Gradle совсем не больно. Но все же, для того, чтобы Gradle был установлен в целевой системе вместе с JDK / JRE, требуется некоторая предварительная работа. Это может быть не проблема, скажем, для разработки, так как очень вероятно, что вы все равно (и многие другие) были бы установлены.
Однако в случае облачных развертываний или конвейеров CI / CD это может быть проблемой, что может повлечь за собой дополнительные затраты с точки зрения работы и / или обслуживания. Можем ли мы найти способ избавиться от таких издержек и полностью положиться на Docker ? Да, фактически мы можем, приняв многоэтапные сборки , одно из недавних дополнений в наборе функций Docker .
Если вам интересно, как это может помочь нам, вот идея. По сути, мы собираемся следовать обычной процедуре создания образа из Dockerfile . Но Dockerfile на самом деле будет содержать два определения изображений. Первый (основанный на одном из официальных образов Gradle ) инструктирует Docker запустить сборку Gradle нашего приложения Spring Boot . Второй выбрал бы двоичные файлы, созданные первым изображением, и создал окончательный образ Docker с нашим приложением Spring Boot, запечатанным внутри (так же, как мы делали раньше).
  Возможно, лучше один раз увидеть, чем пытаться объяснить.  Файл Dockerfile.build ниже иллюстрирует эту идею в действии, используя инструкции многоступенчатой сборки . 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
 | 
FROM gradle:4.3.0-jdk8-alpineADD src srcADD build.gradle .ADD gradle.properties .RUN gradle buildFROM openjdk:8-jdk-alpineARG BUILD_VERSIONENV DB_HOST localhostENV DB_PORT 3306COPY --from=0 /home/gradle/build/libs/spring-boot-webapp-${BUILD_VERSION}.jar spring-boot-webapp.jarEXPOSE 19900ENTRYPOINT exec java $JAVA_OPTS -Ddb.host=$DB_HOST -Ddb.port=$DB_PORT -jar /spring-boot-webapp.jar | 
  Первая часть определения Dockerfile описывает изображение на основе gradle:4.3.0-jdk8-alpine .  Поскольку наш проект довольно мал, мы просто копируем все источники внутри образа и запускаем gradle build (эта команда будет выполняться Docker во время gradle build образа).  Результатом сборки будет uber-jar, который мы openjdk:8-jdk-alpine в другое определение изображения, на этот раз на основе openjdk:8-jdk-alpine .  Это составило бы наше окончательное изображение, которое мы могли бы создать, используя инструмент командной строки docker . 
| 
 1 
2 
3 
4 
5 
 | 
docker image build \  --build-arg BUILD_VERSION=0.0.1-SNAPSHOT \  -f Dockerfile.build \  -t jcg/spring-boot-webapp:latest \  -t jcg/spring-boot-webapp:0.0.1-SNAPSHOT . | 
После командного соревнования мы должны увидеть наше недавно испеченное изображение в списке доступных изображений Docker .
| 
 1 
2 
3 
4 
5 
 | 
$ docker image lsREPOSITORY               TAG            IMAGE ID       CREATED           SIZEjcg/spring-boot-webapp   0.0.1-SNAPSHOT  02abf724da64  10 seconds ago    133MBjcg/spring-boot-webapp   latest          02abf724da64  10 seconds ago    133MB... | 
Многоуровневые сборки имеют большой потенциал, но даже для такого простого приложения, как наше, они заслуживают внимания.
5. Мавен и Докер
  Давайте немного переключимся и посмотрим, как Apache Maven использует управление сборкой для приложений Dropwizard .  Для этого подраздела вам потребуется установить Apache Maven 3.2.5 на компьютере разработчика (однако, если у вас уже установлен Apache Maven 3.2.1 или более поздней версии, вы можете просто придерживаться его). 
Шаги, которые мы должны выполнить, в основном идентичны тем, что мы обсуждали для сборок Gradle , изменения в основном только в плагинах:
- Плагин Maven Shade
 - Плагин Spotify Docker Maven
 
  Плагин Maven Shade создает uber-jar, который позже будет использоваться плагином Spotify Docker Maven для создания образа Docker .  Без суеты, давайте посмотрим на файл pom.xml . 
| 
 001 
002 
003 
004 
005 
006 
007 
008 
009 
010 
011 
012 
013 
014 
015 
016 
017 
018 
019 
020 
021 
022 
023 
024 
025 
026 
027 
028 
029 
030 
031 
032 
033 
034 
035 
036 
037 
038 
039 
040 
041 
042 
043 
044 
045 
046 
047 
048 
049 
050 
051 
052 
053 
054 
055 
056 
057 
058 
059 
060 
061 
062 
063 
064 
065 
066 
067 
068 
069 
070 
071 
072 
073 
074 
075 
076 
077 
078 
079 
080 
081 
082 
083 
084 
085 
086 
087 
088 
089 
090 
091 
092 
093 
094 
095 
096 
097 
098 
099 
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
119 
120 
121 
122 
123 
124 
125 
126 
127 
128 
129 
130 
131 
132 
133 
134 
135 
136 
137 
138 
139 
140 
141 
142 
143 
144 
145 
146 
147 
148 
149 
150 
 | 
<project  xmlns=http://maven.apache.org/POM/4.0.0  xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0  <modelVersion>4.0.0</modelVersion>  <groupId>com.javacodegeeks</groupId>  <artifactId>dropwizard-webapp</artifactId>  <version>0.0.1-SNAPSHOT</version>  <packaging>jar</packaging>  <properties>      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  </properties>  <dependencyManagement>    <dependencies>      <dependency>        <groupId>io.dropwizard</groupId>        <artifactId>dropwizard-bom</artifactId>        <version>1.2.0</version>        <type>pom</type>        <scope>import</scope>      </dependency>    </dependencies>  </dependencyManagement>  <dependencies>    <dependency>      <groupId>io.dropwizard</groupId>      <artifactId>dropwizard-core</artifactId>    </dependency>    <dependency>      <groupId>io.dropwizard</groupId>      <artifactId>dropwizard-hibernate</artifactId>    </dependency>             <dependency>      <groupId>mysql</groupId>      <artifactId>mysql-connector-java</artifactId>      <version>8.0.7-dmr</version>    </dependency>    <dependency>      <groupId>io.dropwizard.modules</groupId>      <artifactId>dropwizard-flyway</artifactId>      <version>1.2.0-1</version>    </dependency>             <dependency>      <groupId>com.google.guava</groupId>      <artifactId>guava</artifactId>    </dependency>         <dependency>      <groupId>junit</groupId>      <artifactId>junit</artifactId>      <scope>test</scope>    </dependency>  </dependencies>  <build>    <plugins>      <plugin>        <groupId>org.apache.maven.plugins</groupId>        <artifactId>maven-compiler-plugin</artifactId>        <version>3.1</version>        <configuration>          <source>1.8</source>          <target>1.8</target>        </configuration>      </plugin>      <plugin>        <groupId>org.apache.maven.plugins</groupId>        <artifactId>maven-jar-plugin</artifactId>        <version>3.0.2</version>        <configuration>          <archive>            <manifest>              <mainClass>com.javacodegeeks.docker.AllApiApp</mainClass>            </manifest>          </archive>        </configuration>      </plugin>      <plugin>        <groupId>org.apache.maven.plugins</groupId>        <artifactId>maven-shade-plugin</artifactId>        <version>3.1.0</version>        <configuration>          <filters>            <filter>              <artifact>*:*</artifact>              <excludes>                <exclude>META-INF/*.SF</exclude>                <exclude>META-INF/*.DSA</exclude>                <exclude>META-INF/*.RSA</exclude>              </excludes>            </filter>          </filters>        </configuration>        <executions>          <execution>            <phase>package</phase>            <goals>              <goal>shade</goal>            </goals>            <configuration>              <transformers>                <transformerimplementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">                                                    <mainClass>com.javacodegeeks.dw.AppStarter</mainClass>                </transformer>              </transformers>            </configuration>          </execution>        </executions>      </plugin>      <plugin>        <groupId>com.spotify</groupId>        <artifactId>docker-maven-plugin</artifactId>        <version>1.0.0</version>        <configuration>          <imageName>jcg/dropwizard-webapp:${project.version}</imageName>          <dockerDirectory>src/main/docker</dockerDirectory>          <resources>            <resource>              <targetPath>/</targetPath>              <directory>${project.build.directory}</directory>              <include>${project.build.finalName}.jar</include>            </resource>            <resource>              <targetPath>/</targetPath>              <directory>${project.basedir}</directory>              <include>application.yml</include>            </resource>          </resources>          <buildArgs>            <BUILD_VERSION>${project.version}</BUILD_VERSION>          </buildArgs>          <imageTags>            <tag>latest</tag>          </imageTags>        </configuration>      </plugin>    </plugins>  </build></project> | 
Честно говоря, это выглядит значительно более многословно, чем сборка Gradle , но если мы на секунду представим, что все теги XML пропали, мы получим в основном идентичное определение, по крайней мере, в случае плагинов Docker . Dockerfile немного отличается, хотя:
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
 | 
FROM openjdk:8-jdk-alpineARG BUILD_VERSIONENV DB_HOST localhostENV DB_PORT 3306ADD dropwizard-webapp-${BUILD_VERSION}.jar dropwizard-webapp.jarADD application.yml application.yml ADD docker-entrypoint.sh docker-entrypoint.shRUN chmod a+x /docker-entrypoint.shEXPOSE 19900 19901ENTRYPOINT ["/docker-entrypoint.sh"] | 
  Из-за особенностей приложения Dropwizard , мы должны связать файл конфигурации, в нашем случае application.yml , вместе с приложением.  Вместо того, чтобы выставлять только один порт 19900 , мы должны выставить другой, 19901 , для административных задач.  И последнее, но не менее важное: мы предоставляем скрипт для инструкции ENTRYPOINT , docker-entrypoint.sh . 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
 | 
#!/bin/shset -ejava $JAVA_OPTS -DDB_HOST=$DB_HOST -DDB_PORT=$DB_PORT -jar /dropwizard-webapp.jar db migrate application.ymlif [ ! $? -ne 0 ]; then  exec java $JAVA_OPTS -DDB_HOST=$DB_HOST -DDB_PORT=$DB_PORT -jar /dropwizard-webapp.jar server application.yml fiexec "$@" | 
  Причиной добавления некоторой сложности здесь является то, что по умолчанию пакет дополнений Dropwizard Flyway не выполняет автоматическую миграцию схемы базы данных.  Мы могли бы обойти это, но самый простой способ — запустить команду db migrate перед запуском приложения Dropwizard .  Это именно то, что мы делаем внутри сценария оболочки выше.  Теперь пришло время запустить сборку! 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
 | 
>  mvn clean package docker:build...Successfully tagged jcg/dropwizard-webapp:0.0.1-SNAPSHOT[INFO] Built jcg/dropwizard-webapp:0.0.1-SNAPSHOT[INFO] Tagging jcg/dropwizard-webapp:0.0.1-SNAPSHOT with latest[INFO] ---------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ---------------------------------------------------------... | 
Давайте посмотрим, будет ли наше изображение доступно локально на этот раз.
| 
 1 
2 
3 
4 
5 
 | 
> docker image lsREPOSITORY               TAG            IMAGE ID      CREATED             SIZEjcg/dropwizard-webapp    0.0.1-SNAPSHOT fa9c310683b1  20 seconds ago      128MBjcg/dropwizard-webapp    latest         fa9c310683b1  20 seconds ago      128MB... | 
Отлично, предполагая, что контейнер MySQL запущен и работает (эта часть не меняется вообще, мы могли бы использовать ту же команду из предыдущего раздела ), мы могли бы просто запустить наш контейнер приложения Dropwizard .
| 
 1 
2 
3 
4 
5 
6 
 | 
docker run -d --rm \  --name dropwizard-webapp \  -p 19900:19900 \  -p 19901:19901 \  -e DB_HOST=`docker inspect --format '{{ .NetworkSettings.IPAddress }}' mysql` \  jcg/dropwizard-webapp:0.0.1-SNAPSHOT | 
  Мы также отображаем порты 19900 и 19901 из контейнера на хост, чтобы мы могли использовать localhost качестве имени хоста в curl . 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
 | 
$ curl -X POST http://localhost:19900/tasks \   -d '[{"title": "Task #1", "description": "Sample Task"}]' \   -H "Content-Type: application/json"[  {    "id":1,    "title":"Task #1",    "description":"Sample Task"  }] | 
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
 | 
$ curl http://localhost:19900/tasks[  {    "id":1,    "title":"Task #1",    "description":"Sample Task"  }] | 
| 
 1 
2 
3 
4 
5 
6 
7 
 | 
$ curl http://localhost:19900/tasks/1{  "id":1,  "title":"Task #1",  "description":"Sample Task"} | 
  Обратите внимание, что при сопоставлении портов хоста мы можем запустить либо jcg/dropwizard-webapp:0.0.1-SNAPSHOT либо контейнер jcg/spring-boot-webapp:0.0.1-SNAPSHOT , но не оба одновременно, так как к неизбежным конфликтам портов.  Мы просто используем один и тот же порт для удобства, но в большинстве случаев вы будете использовать динамические привязки портов и не увидите, что эта проблема происходит. 
6. Maven на докере
Та же техника использования многоэтапных сборок в равной степени применима к проектам, которые используют Apache Maven для сборки и управления зависимостями (к счастью, существуют официальные изображения Apache Maven, опубликованные на Docker Hub ).
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
 | 
FROM maven:3.5.2-jdk-8-alpineADD src srcADD pom.xml .RUN mvn packageFROM openjdk:8-jdk-alpineARG BUILD_VERSIONENV DB_HOST localhostENV DB_PORT 3306COPY --from=0 /target/dropwizard-webapp-${BUILD_VERSION}.jar dropwizard-webapp.jarADD application.yml application.yml ADD src/main/docker/docker-entrypoint.sh docker-entrypoint.shRUN chmod a+x /docker-entrypoint.shEXPOSE 19900 19901ENTRYPOINT ["/docker-entrypoint.sh"] | 
Немного добавить сюда, когда мы взломали работу многоступенчатых сборок , поэтому давайте создадим окончательный образ с помощью инструмента командной строки docker .
| 
 1 
2 
3 
4 
5 
 | 
docker image build \  --build-arg BUILD_VERSION=0.0.1-SNAPSHOT \  -f Dockerfile.build \  -t jcg/dropwizard-webapp:latest \  -t jcg/dropwizard-webapp:0.0.1-SNAPSHOT . | 
И убедитесь, что изображение появляется в списке доступных изображений Docker .
| 
 1 
2 
3 
4 
5 
 | 
> docker image lsREPOSITORY             TAG             IMAGE ID       CREATED          SIZEjcg/dropwizard-webapp  0.0.1-SNAPSHOT  5b006fcc9a1d   26 seconds ago   128MBjcg/dropwizard-webapp  latest          5b006fcc9a1d   26 seconds ago   128MB... | 
Это довольно круто, если честно. Прежде чем закончить обсуждение многоэтапных сборок , давайте коснемся варианта использования, с которым вы наверняка столкнетесь: проверка проекта из системы контроля версий. Примеры, которые мы видели до сих пор, предполагают, что проект доступен локально, но мы могли бы клонировать его из удаленного репозитория как часть определения многоступенчатых сборок .
7. Выводы
В этом разделе руководства мы увидели несколько примеров того, как популярные инструменты управления сборкой и зависимостями, а именно Apache Maven и Gradle , поддерживают упаковку приложений Java в виде образов Docker . Мы также потратили некоторое время на обсуждение многоэтапных сборок и возможностей, которые они открывают для реализации переносимых конвейеров сборки с нулевой зависимостью (буквально!).
8. Что дальше
В следующем разделе руководства мы рассмотрим, как Docker может упростить процессы и практики разработки, особенно в отношении работы с хранилищами данных и внешними (или даже внутренними) сервисами.
Полные исходные коды проекта доступны для скачивания .