Streamlining Java Application Deployment with Docker and Maven

Why Containerization Matters

The way we build, package and deploy applications is undergoing a dramatic transformation. Driven by the need for faster innovation and more efficient use of computing resources, a major shift is underway from traditional deployment models to containerized applications.

Containers provide a way to package an application along with its dependencies in an isolated, portable unit that can reliably run in any environment. By abstracting the application from the underlying infrastructure, containers enable development teams to build software faster and more consistently. The leading container platform, Docker, has seen explosive growth in recent years. According to the 2020 Jetbrains Developer Survey, 79% of developers now use Docker, up from 53% in 2016.

This rapid adoption of containerization is fueling a broader transition to cloud-native application architectures and microservices. With microservices, large monolithic codebases are decomposed into smaller, independently deployable services that communicate via APIs. Each microservice can be developed, tested, scaled and updated separately, enabling much faster and more agile development cycles. Containers are the ideal packaging format for microservices, providing the right level of isolation and portability.

The cloud-native model depends on the ability to frequently update and deploy a large number of services, which requires a high degree of automation. By building container images as part of their continuous integration process and deploying them via an automated pipeline, teams can reduce manual effort and achieve faster, more reliable releases.

Integrating Docker with Maven Builds

For organizations building applications on the Java platform, Apache Maven is the most widely used tool for managing builds and dependencies. Maven simplifies Java development by providing a standardized, declarative approach to build configuration and an extensive ecosystem of plugins.

While Docker itself is not Java-specific, integrating Docker image creation into the Maven build process allows Java shops to automate their container workflows without needing to adopt an entirely separate set of tools and practices. By building a Docker image for the application as part of the standard mvn package build, the creation of an up-to-date application container becomes automatic. This image can then be automatically pushed to a registry and deployed via Docker commands in a continuous delivery pipeline.

Here‘s what a simple Maven + Docker continuous delivery flow might look like:

  1. Developer pushes code change to Git repository
  2. CI server detects the change and triggers a Maven build
  3. Maven compiles the code and runs unit tests
  4. Maven builds a Docker image for the app and tags it with the app version
  5. Maven pushes the versioned image to a Docker registry
  6. CD server deploys the new image to a test environment by pulling it from the registry and running it
  7. After passing integration tests, CD server deploys the new image to production

By automating the creation and deployment of the Docker image for every version of the code, this flow ensures the application can be reliably released at any time, without manual build or deployment steps that often introduce errors.

Step-by-Step: Enabling Docker Builds in Maven

To add creation of a Docker image to a Maven build, we‘ll use the maven-resources-plugin to copy the Dockerfile and other resources to the target directory and the maven-antrun-plugin to execute the actual Docker build and push. Here are the steps:

  1. Create a Dockerfile in src/main/docker that specifies how to build an image for the application, including the base image to use, any compile-time dependencies to install, and instructions for adding the compiled application code to the image. Example:
  FROM openjdk:11-jre-slim

  COPY target/myapp.jar /app.jar

  ENTRYPOINT ["java", "-jar", "/app.jar"]
  1. Add the maven-resources-plugin configuration to the pom.xml file to copy the Dockerfile and other resources to the target directory:
  <plugin>
    <artifactId>maven-resources-plugin</artifactId>
    <executions>
      <execution>
        <id>copy-dockerfile</id>
        <phase>package</phase>
        <goals>
          <goal>copy-resources</goal>
        </goals>
        <configuration>
          <outputDirectory>${project.build.directory}</outputDirectory>
          <resources>
            <resource>
              <directory>src/main/docker</directory>
              <filtering>true</filtering>
            </resource>
          </resources>
        </configuration>
      </execution>
    </executions>
  </plugin>
  1. Add the maven-antrun-plugin configuration to the pom.xml to execute the Docker build and push commands:
  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-antrun-plugin</artifactId>
    <executions>
      <execution>
        <id>build-docker-image</id>
        <phase>package</phase>
        <goals>
          <goal>run</goal>
        </goals>
        <configuration>
          <target>
            <exec executable="docker" dir="${project.build.directory}">
              <arg value="build"/>
              <arg value="-t"/>
              <arg value="myapp:${project.version}"/>
              <arg value="."/>
            </exec>
          </target>
        </configuration>
      </execution>
      <execution>
        <id>push-docker-image</id>
        <phase>deploy</phase>
        <goals>
          <goal>run</goal>
        </goals>
        <configuration>
          <target>
            <exec executable="docker">
              <arg value="push"/>
              <arg value="myrepo/myapp:${project.version}"/>
            </exec>
          </target>
        </configuration>
      </execution>
    </executions>
  </plugin>

With these additions, every time the Maven package phase runs, it will copy the Docker resources to the target directory and run docker build to create an image tagged with the Maven version. When the deploy phase runs (manually or via a CI/CD tool), it will run docker push to upload the versioned image to the configured registry.

Some additional Maven plugins that are useful for working with Docker include:

  • fabric8io/docker-maven-plugin – Provides goals for building, pushing, starting and stopping containers directly from the Maven build
  • spotify/dockerfile-maven – Integrates Maven with a Dockerfile to automatically build and push an image
  • google/jib – Build container images directly from Java applications without a Dockerfile

These plugins provide a higher level of abstraction and tighter integration between Maven and Docker workflows.

Real-World Examples

To illustrate the power of integrating Maven and Docker in a real continuous delivery workflow, let‘s look at a few examples of well-known open source projects that use this approach.

The popular Netflix microservices orchestration platform Conductor uses Maven to compile the application and the fabric8 docker-maven-plugin to build Docker images for each of its components. Images are automatically built and pushed to a registry when a new version is released. Netflix engineers can then deploy Conductor by pulling and running the versioned images from the registry.

Leading big data processing engine Apache Spark uses the spotify dockerfile-maven plugin to build a Docker image for each Spark release. The image contains the compiled Spark binaries and is pushed to Docker Hub, where it has been pulled over a billion times. Having an official Docker image for each release makes it easy for developers to get started with Spark and enables deploying it in containerized environments like Kubernetes.

The Future of Java Application Deployment

As Java development teams face pressure to deliver new features and fixes faster than ever, automating the build and deployment process has become critical. The Maven + Docker combination showcased in this article is a powerful tool for enabling continuous delivery of Java applications.

By integrating the creation of a Docker image into the standard Maven build process, developers can package their application in a portable container that is ready to be deployed at any time. The image can be automatically pushed to a registry and pulled for deployments to dev, test and production environments. This automation reduces errors from manual build and deploy steps and shortens cycle times.

Beyond the immediate efficiency gains, integrating containers into the Java development workflow also helps teams adopt modern microservices architectures and cloud-native practices. Containerization is an essential enabler for deploying and orchestrating large numbers of microservices and effortlessly scaling them up and down in response to demand.

According to the CNCF 2020 Survey, the use of containers in production has increased to 92%, with a further 85% of respondents using Kubernetes in production. As the Java ecosystem continues its shift to cloud-native, the ability to automatically build and deploy applications as containers will become an indispensable skill. Implementing Maven + Docker automation is a great way to start this journey.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *