Skip to content

Spring Boot with Docker :: Learn how to create a Docker container from a Spring Boot application with Maven or Gradle

Notifications You must be signed in to change notification settings

Ruc12/gs-spring-boot-docker

 
 

Repository files navigation

This guide walks you through the process of building a Docker image for running a Spring Boot application.

What you’ll build

Docker is a Linux container management toolkit with a "social" aspect, allowing users to publish container images and consume those published by others. A Docker image is a recipe for running a containerized process, and in this guide we will build one for a simple Spring boot application.

Note
There is also a Topical Guide on Docker, which covers a wider range of choices that we have here, and in much more detail.

What you’ll need

If you are NOT using a Linux machine, you will need a virtualized server. By installing VirtualBox, other tools like the Mac’s boot2docker, can seamlessly manage it for you. Visit VirtualBox’s download site and pick the version for your machine. Download and install. Don’t worry about actually running it.

You will also need Docker, which only runs on 64-bit machines. See https://docs.docker.com/installation/#installation for details on setting Docker up for your machine. Before proceeding further, verify you can run docker commands from the shell. If you are using boot2docker you need to run that first.

Set up a Spring Boot app

Now you can create a simple application.

src/main/java/hello/Application.java

link:complete/src/main/java/hello/Application.java[role=include]

The class is flagged as a @SpringBootApplication and as a @RestController, meaning it’s ready for use by Spring MVC to handle web requests. @RequestMapping maps / to the home() method which just sends a 'Hello World' response. The main() method uses Spring Boot’s SpringApplication.run() method to launch an application.

Now we can run the application without the Docker container (i.e. in the host OS).

If you are using Gradle, execute:

./gradlew build && java -jar build/libs/gs-spring-boot-docker-0.1.0.jar

If you are using Maven, execute:

./mvnw package && java -jar target/gs-spring-boot-docker-0.1.0.jar

and go to localhost:8080 to see your "Hello Docker World" message.

Containerize It

Docker has a simple "Dockerfile" file format that it uses to specify the "layers" of an image. So let’s go ahead and create a Dockerfile in our Spring Boot project:

Dockerfile

FROM openjdk:8-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

You can run it (if you are using Maven) with

$ docker build -t springio/gs-spring-boot-docker .

or (if you are using Gradle):

$ docker build --build-arg JAR_FILE=build/libs/*.jar -t springio/gs-spring-boot-docker .

This command builds an image and tags it as springio/gs-spring-boot-docker.

This Dockerfile is very simple, but that’s all you need to run a Spring Boot app with no frills: just Java and a JAR file. The build will create a spring user and a spring group to run the application. It will then COPY the project JAR file into the container as "app.jar" that will be executed in the ENTRYPOINT. The array form of the Dockerfile ENTRYPOINT is used so that there is no shell wrapping the java process. The Topical Guide on Docker goes into this topic in more detail.

Note
To reduce Tomcat startup time we formerly added a system property pointing to "/dev/urandom" as a source of entropy. This is not necessary anymore with JDK 8 or later.

Running applications with user privileges helps to mitigate some risks (see for example a thread on StackExchange). So, an important improvement to the Dockerfile is to run the app as a non-root user:

Dockerfile

FROM openjdk:8-jdk-alpine
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

Also, to take advantage of the clean separation between dependencies and application resources in a Spring Boot fat jar file, we will use a slightly different implementation of the Dockerfile:

Dockerfile

link:complete/Dockerfile[role=include]

This Dockerfile has a DEPENDENCY parameter pointing to a directory where we have unpacked the fat jar. From a Maven build:

$ mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)

or from a Gradle build:

$ mkdir -p build/dependency && (cd build/dependency; jar -xf ../libs/*.jar)

If we get that right, it already contains a BOOT-INF/lib directory with the dependency jars in it, and a BOOT-INF/classes directory with the application classes in it. Notice that we are using the application’s own main class hello.Application (this is faster than using the indirection provided by the fat jar launcher).

Note
exploding the jar file can result in the classpath order being different at runtime. A well-behaved and well-written application should not care about this, but you may see behaviour changes if the dependencies are not carefully managed.
Note
if you are using boot2docker you need to run it first before you do anything with the Docker command line or with the build tools (it runs a daemon process that handles the work for you in a virtual machine).

To build the image you can use the Docker command line. For example:

$ docker build -t springio/gs-spring-boot-docker .

From a Gradle build, add the explicit build args:

$ docker build --build-arg DEPENDENCY=build/dependency -t springio/gs-spring-boot-docker .
Tip
Of course if you only used Gradle, you could just change the Dockerfile to make the default value of DEPENDENCY match the location of the unpacked archive.

Instead of building with the Docker command line, you might want to use a build plugin. Google has an open source tool called Jib that has Maven and Gradle plugins. Probably the most interesting thing about it is that you don’t need docker - it builds the image using the same standard output as you get from docker build but doesn’t use docker unless you ask it to - so it works in environments where docker is not installed (not uncommon in build servers).

Build a Docker Image with Maven

To get started quickly, you can run Jib without even changing your pom.xml:

$ ./mvnw com.google.cloud.tools:jib-maven-plugin:dockerBuild -Dimage=springio/gs-spring-boot-docker

To push to a Docker registry you use the build goal, instead of dockerBuild, i.e.

$ ./mvnw com.google.cloud.tools:jib-maven-plugin:build -Dimage=springio/gs-spring-boot-docker

To do that you will need to have permission to push to Dockerhub, which you won’t have by default. Change the image prefix to your own Dockerhub ID, and docker login to make sure you are authenticated before you run Maven.

Build a Docker Image with Gradle

If you are using Gradle you need to add a new plugin like this:

build.gradle

plugins {
  ...
  id 'com.google.cloud.tools.jib' version '1.8.0'
}

or in the older style used in the Getting Started Guides:

build.gradle

buildscript {
    repositories {
      maven {
        url "https://plugins.gradle.org/m2/"
      }
      mavenCentral()
    }
    dependencies {
        classpath('org.springframework.boot:spring-boot-gradle-plugin:2.2.1.RELEASE')
link:complete/build.gradle[role=include]
    }
}
link:complete/build.gradle[role=include]

You can build a tagged docker image with Gradle in one command:

$ ./gradlew jibDockerBuild --image=springio/gs-spring-boot-docker

As with the Maven build, there is also a build task to build and push to the Docker registry:

$ ./gradlew jib --image=springio/gs-spring-boot-docker

If you have authenticated with docker on the command line, the image push will authenticate from your local ~/.docker configuration.

After the Push

A "docker push" (or build plugin with "jib") in the example will fail for you (unless you are part of the "springio" organization at Dockerhub), but if you change the configuration to match your own docker ID then it should succeed, and you will have a new tagged, deployed image.

You do NOT have to register with docker or publish anything to run a docker image that was built locally. If you built with Docker (from the command line or from Jib), you still have a locally tagged image, and you can run it like this:

$ docker run -p 8080:8080 -t springio/gs-spring-boot-docker
....
2015-03-31 13:25:48.035  INFO 1 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-03-31 13:25:48.037  INFO 1 --- [           main] hello.Application                        : Started Application in 5.613 seconds (JVM running for 7.293)

The application is then available on http://localhost:8080 (visit that and it says "Hello Docker World").

Note

When using a Mac with boot2docker, you typically see things like this at startup:

Docker client to the Docker daemon, please set:
    export DOCKER_CERT_PATH=/Users/gturnquist/.boot2docker/certs/boot2docker-vm
    export DOCKER_TLS_VERIFY=1
    export DOCKER_HOST=tcp://192.168.59.103:2376

To see the app, you must visit the IP address in DOCKER_HOST instead of localhost. In this case, https://192.168.59.103:8080, the public facing IP of the VM.

When it is running you can see in the list of containers, e.g:

$ docker ps
CONTAINER ID        IMAGE                                   COMMAND                  CREATED             STATUS              PORTS                    NAMES
81c723d22865        springio/gs-spring-boot-docker:latest   "java -Djava.secur..."   34 seconds ago      Up 33 seconds       0.0.0.0:8080->8080/tcp   goofy_brown

and to shut it down again you can docker stop with the container ID from the listing above (yours will be different):

$ docker stop goofy_brown
81c723d22865

If you like you can also delete the container (it is persisted in your filesystem under /var/lib/docker somewhere) when you are finished with it:

$ docker rm goofy_brown

Using Spring Profiles

Running your freshly minted Docker image with Spring profiles is as easy as passing an environment variable to the Docker run command

$ docker run -e "SPRING_PROFILES_ACTIVE=prod" -p 8080:8080 -t springio/gs-spring-boot-docker

or

$ docker run -e "SPRING_PROFILES_ACTIVE=dev" -p 8080:8080 -t springio/gs-spring-boot-docker

Debugging the application in a Docker container

To debug the application JPDA Transport can be used. So we’ll treat the container like a remote server. To enable this feature pass a java agent settings in JAVA_OPTS variable and map agent’s port to localhost during a container run. With the Docker for Mac there is limitation due to that we can’t access container by IP without black magic usage.

$ docker run -e "JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n" -p 8080:8080 -p 5005:5005 -t springio/gs-spring-boot-docker

Summary

Congratulations! You’ve just created a Docker container for a Spring Boot app! Spring Boot apps run on port 8080 inside the container by default and we mapped that to the same port on the host using "-p" on the command line.

About

Spring Boot with Docker :: Learn how to create a Docker container from a Spring Boot application with Maven or Gradle

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Java 73.4%
  • Shell 17.4%
  • Dockerfile 9.2%