wouterd / docker-maven-plugin

A maven plugin to manage docker containers and images for integration tests.
Apache License 2.0
78 stars 24 forks source link
docker-container docker-daemon docker-image java

docker-maven-plugin

No Maintenance Intended

Join the chat at https://gitter.im/wouterd/docker-maven-plugin

Build Status

A maven plugin to create, test and publish docker containers and images for maven projects. Can also be used to integration test your application using docker containers.

About this document

As the documentation evolves with different plugin versions, be sure that before you read on that:

The README of the master branch will cover the current development version and not cover the current released version.

Minimum versions required for the plugin to work

Current Functionality:

Usage

Current release version: 5.0.0

Current snapshot version: 5.0.1-SNAPSHOT

Example

The following is a snippet from the pom for one of the integration test projects in this project. You can find the full pom.xml here: pom.xml

<plugin>
  <groupId>net.wouterdanes.docker</groupId>
  <artifactId>docker-maven-plugin</artifactId>
  <version>5.0.0</version>
  <executions>
    <execution>
      <id>package</id>
      <goals>
        <goal>build-images</goal>
      </goals>
      <configuration>
        <images>
          <image>
            <id>app</id>
            <dockerFile>${project.basedir}/src/main/docker/Dockerfile</dockerFile>
            <artifacts>
              <artifact>
                <file>${project.build.directory}/discuss-jar-with-dependencies.jar</file>
              </artifact>
            </artifacts>
          </image>
        </images>
      </configuration>
    </execution>
    <execution>
      <id>start</id>
      <goals>
        <goal>start-containers</goal>
      </goals>
      <configuration>
        <!-- You can set forceCleanup to true to stop and remove started containers
             at the end of the build even if the stop-containers goal is not executed
             (useful for preventing Ctrl+C causing dangling containers) -->
        <forceCleanup>false</forceCleanup>
        <containers>
          <container>
            <id>mongo</id>
            <image>mongo:2.6</image>
            <waitForStartup>waiting for connections on port 27017</waitForStartup>
          </container>
          <container>
            <id>app</id>
            <image>app</image>
            <links>
              <link>
                <containerId>mongo</containerId>
                <containerAlias>mongo</containerAlias>
              </link>
            </links>
            <env>
              <APP_MESSAGE>Hello, world!</APP_MESSAGE>
            </env>
            <waitForStartup>Ratpack started for</waitForStartup>
          </container>
          <container>
            <id>app2</id>
            <image>app</image>
            <links>
              <link>
                <containerId>mongo</containerId>
                <containerAlias>mongo</containerAlias>
              </link>
            </links>
            <env>
              <APP_MESSAGE>I am, so I message</APP_MESSAGE>
            </env>
            <waitForStartup>Ratpack started for</waitForStartup>
          </container>
        </containers>
      </configuration>
    </execution>
    <execution>
      <id>commit</id>
      <configuration>
        <containers>
          <container>
            <id>app2</id>
            <repo>me/app</repo>
            <tag>1.0</tag>
            <push>true</push>
          </container>
        </containers>
      </configuration>
      <goals>
        <goal>commit-container</goal>
      </goals>
    </execution>
    <execution>
      <id>stop</id>
      <goals>
        <goal>stop-containers</goal>
      </goals>
    </execution>
    <execution>
      <id>verify</id>
      <goals>
        <goal>verify</goal>
      </goals>
    </execution>
  </executions>
</plugin>

The above pom.xml element includes the plugin and starts builds an image from the project. Then it starts some containers in the pre-integration-test phase, including the built container and stops those in the post-integration-test phase. Under <configuration> add some containers. By giving them an id, you can reference them later and the ID is also used in the port mapping properties. The <image> tag specifies the docker image to start.

By default, all exposed ports are published on the host. The following two properties are set per exposed port:

You can pass those project properties over to your integration test and use them to connect to your application.

The plugin will connect to a docker instance over HTTP, linux socket support will be added after 1.0. It will look up the host/port of docker in the following way:

Environment Variables

Environment variables can be passed to containers using the following configuration syntax:

        <container>
            <id>app</id>
            <image>app</image>
            <env>
                <VARIABLE_NAME>variable value<VARIABLE_NAME>
            <env>
        </container>

Setting the hostname

The hostname can be set by adding a element to a container configuration.

        <container>
            <id>app</id>
            <image>app</image>
            <hostname>appserver1</hostname>
        </container>

Setting the hostname

The macAddress can be set by adding a element to a container configuration.

        <container>
            <id>app</id>
            <image>app</image>
            <macAddress>12:34:56:78:9a:bc</macAddress>
        </container>

Linking containers

Containers can be linked, similar to the --link name:alias parameter of the docker run command. The configuration snippet looks as follows:

        <container>
            <id>app</id>
            <image>app</image>
            <links>
                <link>
                    <containerId>mongo</containerId>
                    <containerAlias>mongo</containerAlias>
                </link>
            </links>
        </container>

The containerId is the id specified in another <container> definition. It will be replaced with the container name of the started container when the plugin is executed. The containerAlias is the name of the container being linked inside the container that links the container. It's also the hostname of the linked container for the linking container. In the case of the above XML snippet, I can now reach the mongodb instance using mongo:27017 as the connection string.

Wait for a container to finish starting up

You might want to wait for your application to finish initialization before you start running integration tests. The plugin allows you to do a global regular expression find on the stdout + stderr of your container to see if the container has finished initialization. To check if a tomcat container has started up, you could configure the following:

    <container>
        <id>app-server</id>
        <image>myAppServer</image>
        <waitForStartup>Server startup in</waitForStartup>
    </container>

The <waitForStartup/> tag can contain any valid java regular expression.

build-images goal

The build-images goal allows you to build a docker image based on a list of files, one of which must be a Dockerfile. Below is an example snippet.

      <execution>
        <id>build</id>
        <goals>
          <goal>build-images</goal>
        </goals>
        <configuration>
          <images>
            <image>
              <id>nginx</id>
              <dockerFile>${project.basedir}/src/test/resources/Dockerfile</dockerFile>
              <artifacts>
                <artifact>
                  <file>${project.basedir}/src/test/resources/nginx.conf</file>
                  <dest>etc/nginx/nginx.conf</dest>
                </artifact>
                <artifact>
                  <file>${project.basedir}/src/test/resources/sites-available/</file>
                  <dest>etc/nginx/sites-available/</dest>
                </artifact>
              </artifacts>
              <mavenArtifacts>
                <mavenArtifact>
                  <dependency>biz.hochguertel.javaeetutorial.jaxrs:hello:war:1.4</dependency>
                  <dest>test/rest.war</dest>
                </mavenArtifact>
              </mavenArtifacts>
              <buildArguments>
                <foo>bar</foo>
              </buildArguments>
              <keep>true</keep>
              <push>true</push>
              <registry>mydocker-registry.corp.com:5000</registry>
              <nameAndTag>wouterd/my-nginx:1.0</nameAndTag>
            </image>
          </images>
        </configuration>
      </execution>

The configuration works as follows:

stop-containers goal

The stop-containers goal allows you to stop and clean up any containers that were created with the plugin. The logs parameter allows you to specify a folder where the logs of the containres need to go. They will be saved as [container name].log. If you don't specify a folder, no logs will be saved.

commit-containers goal

The commit-containers goals allows you to persist the state of a container to an image. Below is an example snippet.

      <execution>
        <id>commit</id>
        <goals>
          <goal>commit-containers</goal>
        </goals>
        <configuration>
          <containers>
            <container>
              <id>app2</id>
              <repo>me/app</repo>
              <tag>${maven.build.timestamp}</tag>
              <comment>Daily build</comment>
              <author>Jenkins</author>
              <push>true</push>
            </container>
           </containers>
        </configuration>
      </execution>

The configuration works as follows:

tag-images goal

The tag-images goal allows you to assign additional tags to images and optionally flag those tags to be pushed to a Docker image registry in a subsequent push-images execution. Below is an example snippet.

      <execution>
        <id>release</id>
        <goals>
          <goal>tag-images</goal>
        </goals>
        <configuration>
          <images>
            <image>
              <id>nginx</id>
              <tags>
                <tag>goonwarrior/my-nginx:1.0</tag>
                <tag>goonwarrior/my-nginx:latest</tag>
              </tags>
              <push>true</push>
              <registry>mydocker-registry.corp.com:5000</registry>
            </image>
          </images>
        </configuration>
      </execution>

The configuration works as follows:

push-images goal

The push-images goal allows you to push any marked images that were built in a prior execution of the build-images goal to a Docker image registry.

Pushing an image to a private registry (that is, a registry other than https://registry.hub.docker.com/) can be specified in one of two ways.

  1. Embedded in the <nameAndTag>

      e.g.
      `<nameAndTag>myregistry.corpdomain.net:5000/repo:tag</nameAndTag>`
  2. Separately via <registry> parameter of the image or incorporated into the <nameAndTag>.

      e.g.
      `<nameAndTag>repo:tag</nameAndTag>`
      `<registry>myregistry.corpdomain.net</registry>`

These 2 configurations behave slightly differently. In the former, the image is associated with a single long tag and all references to subsequent references to that image (e.g. in FROM statement in a Dockerfile) need to reference the full string. In the latter case, 2 tags are registered, one long, one short, enabling access to the more concise form.

If the registry is omitted, then https://registry.hub.docker.com/ is assumed.

Credentials

Some registries (including https://registry.hub.docker.com/) will require user credentials to perform specific operations. The plugin provides a means to specify these credentials however, at this time they are only used when pushing images. These credentials can be specified in multiple ways.

Credentials within plugin configuration or populated indirectly by Maven properties

Credentials in Maven's global settings.xml

Define a new server in ~/.m2/settings.xml

    <servers>
        <server>
            <id>docker.mycompany.com</id>
            <username>...</username>
            <password>...</password>
            <configuration>
                <email>...</email>
            </configuration>
          </server>
    </servers>

Refer to this server in the plugin configuration

    <plugin>
        <groupId>net.wouterdanes.docker</groupId>
        <artifactId>docker-maven-plugin</artifactId>
        <version>4.1.1</version>
        <configuration>
            <serverId>docker.mycompany.com</serverId>
        </configuration>
        <executions>
            ...
        </executions>
    </plugin>

Priority is given to credentials defined in plugin configuration over the ones from settings.xml.

Using a SNAPSHOT version

The releases of this plugin are deployed to maven central, the SNAPSHOT versions are automatically deployed to the Sonatype OSS repository. To be able to use the SNAPSHOT versions of this plugin, add the following repository to your project POM or settings.xml:

  <pluginRepository>
        <id>sonatype-oss-snapshots</id>
        <name>Sonatype OSS Snapshots</name>
        <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
  </pluginRepository>

Enabling the Remote Api on the Docker Daemon

Normally, docker accepts commands via unix sockets, by default this is /var/run/docker.sock. This plugin uses the REST API that is also packaged with docker, but needs to be enabled. You can enable this by adding a -H option to the daemon startup command, see http://docs.docker.io/reference/commandline/cli/#daemon. To bind the REST API to port 2375 (default) that only listens to the local interface, add this to your daemon startup: -H tcp://127.0.0.1:2375

Skipping execution of the plugin or phases

To skip execution of the plugin, you can set the docker.skip property to true. This can be useful when you want to skip running tests, like: mvn clean verify -Ddocker.skip=true -DskipTests. Each individual execution can be skipped or the plugin as a whole can be skipped by configuring the <skip> property on the <configuration> element of the plugin or an execution. Adding the following profile to your pom.xml will skip the whole plugin when the skipTests property is set:

    <profile>
        <id>skip-docker-plugin-execution</id>
        <activation>
            <property>skipTests</property>
        </activation>
        <properties>
            <docker.skip>true</docker.skip>
        </properties>
    </profile>

Docker providers

Currently the plugin supports two types of docker "providers", which both connect to docker via the remote API (HTTP REST), unix sockets are not yet supported:

The remote provider works for both dockers running on the same system as the client as well as boot2docker or VM based dockers. Just make sure DOCKER_HOST or docker.host points to the IP that is on the host-only network or that has all dynamic docker ports exposed (49xxx). The local provider works when the docker containers are reachable from the client through their IP address, so for example when the client runs on the docker host. Local is also a nice mode to use when consumers of your containers need to connect on the "real port" and cannot connect to a "dynamic port".

You can specify the docker provider using the system property docker.provider, either in the pom or via the command line using -D, for example: mvn clean verify -Prun-its -Ddocker.provider=local

HTTPS support

As per Docker 1.3, the docker daemon can be protected with SSL encryption. For this to work, the client needs to have some certificates and a private key. These by default reside in ~/.docker. Boot2docker now by default since version 1.3 enables SSL encryption. The plugin supports this. As long as you can reach the daemon by doing docker ps on the command line, the plugin should pick up the right environment variables. The environment variables that control this behavior are:

Dependencies:

Building the project

To build the project, you will need Maven and Java8.

To build the project and run all the tests, run:

    mvn clean verify -Prun-its

This will run the build including all integration tests. You should run this at least once before submitting a PR. To just run unit tests, run:

    mvn clean verify

Architecture principles

Future functionality