Search code examples
gradledockerspring-bootdockerfilespring-boot-docker-plugin

Generic Docker Image and Dockerfile for SpringBoot Apps using Gradle/Maven


According to https://spring.io/guides/gs/spring-boot-docker/, we can create Docker Images for SpringBoot applications using hard-coded name and version of the application. For instance:

src/main/docker/Dockerfile

FROM frolvlad/alpine-oraclejdk8:slim
VOLUME /tmp
ADD gs-spring-boot-docker-0.1.0.jar app.jar
RUN sh -c 'touch /app.jar'
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

However, changing the name or the version of the app breaks the Docker Build command that you place in your build.gradle task.

build.gradle

task buildDocker(type: Docker, dependsOn: build) {
  push = true
  applicationName = jar.baseName
  dockerfile = file('src/main/docker/Dockerfile')
  doFirst {
    copy {
      from jar
      into stageDir
    }
  }
}

The command gradle buildDocker builds an image by staging the Dockerfile and the executable Jar from the app, and executing "docker build".

Question

Considering the names are static in Dockerfile, How can I change this setup to not break my builds once I change the version, or even the name my SpringBoot application when building the docker image?


Solution

  • Generic Multi-stage Dockerfile for SpringBoot Apps with Maven

    • I have worked on new versions of a generic Dockerfile for Springboot apps using multi-stage builds instead of maintaining multiple dockerfiles https://docs.docker.com/develop/develop-images/multistage-build
      • This uses Maven. Similar approach can be done for Gradle.
    • This runs the test cases and then builds the runtime image
      • Supporting splitting the downloads of dependencies from running the tests
      • Builds the test stage separately docker build -t tests --target builder .
      • Runs unit tests by default using code coverage
      • Runs integration tests from the switch mvn -s settings.xml -Dtest="!*IT,!*IntegrationTest" -P jacoco test.
    • Supports providing the JAVA_PARAMS and JAVA_OPTS for debugging and anything required
      • If you deploy this image in Docker Swarm or Kubernetes Helm.
    • Provide settings.xml to point to your Maven repo server (public or private)

    The build below will do the following:

    #
    # Build stage to for building the Jar
    #
    FROM maven:3.2.5-jdk-8 as builder
    MAINTAINER marcello.desales@gmail.com
    
    # Only copy the necessary to pull only the dependencies from Intuit's registry
    ADD ./pom.xml /opt/server/pom.xml
    # As some entries in pom.xml refers to the settings, let's keep it same
    ADD ./settings.xml /opt/server/settings.xml
    
    WORKDIR  /opt/server/
    
    # Prepare by downloading dependencies
    RUN mvn -s settings.xml -B -e -C -T 1C org.apache.maven.plugins:maven-dependency-plugin:3.0.2:go-offline
    
    # Run the full packaging after copying the source
    ADD ./src /opt/server/src
    RUN mvn -s settings.xml install -P embedded -Dmaven.test.skip=true -Dmaven.javadoc.skip=true -B -e -o -T 1C verify
    
    # Building only this stage can be done with the --target builder switch
    # 1. Build: docker build -t config-builder --target builder .
    # When running this first stage image, just verify the unit tests
    # Overriden them by removing the "!" for integration tests
    # 2. docker run --rm -ti config-builder mvn -s settings.xml -Dtest="*IT,*IntegrationTest" test
    CMD mvn -s settings.xml -Dtest="!*IT,!*IntegrationTest" -P jacoco test
    
    #
    # Build stage with the runtime jar and resources
    #
    FROM openjdk:8-jre-slim
    
    # Copy from the previous stage
    COPY --from=builder /opt/server/target/*.jar /tmp/
    
    # Just rename the built version
    RUN mkdir /runtime && \
        find /tmp -name "*.jar" ! -name "*sources*" -exec cp -t /runtime {} + && \
        mv /runtime/*.jar /runtime/server.jar && \
        rm -f /tmp/*.jar
    
    # Port used by the server
    EXPOSE 8888
    
    # This is to support HTTPS calls to
    RUN apt-get update && apt-get install -y curl ca-certificates
    RUN update-ca-certificates && \
       mkdir -p /usr/share/ssl/certs && \
       chmod 755 /usr/share/ssl/certs
    
    # What to execute on docker run
    ENTRYPOINT sh -c "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom \
               $JAVA_PARAMS -jar /runtime/server.jar --server.port=8888 $SPRING_BOOT_APP_OPTS"
    

    Build and Run Tests

    • Following the multi-stage build spec, we can now generate only the test image
      • For multiple types of tests (unit, integration), you can split the executions.

    So, the building the tests can be done as follows:

    $ docker build -t generic-dockerfile:tests --target builder .
    Sending build context to Docker daemon  16.82MB
    Step 1/9 : FROM maven:3.2.5-jdk-8 as builder
     ---> 95dd59c15f5d
    Step 2/9 : MAINTAINER marcello.desales@gmail.com
     ---> Using cache
     ---> e4edaeb48381
    Step 3/9 : ADD ./pom.xml /opt/server/pom.xml
     ---> Using cache
     ---> b2d6d834b411
    Step 4/9 : ADD ./settings.xml /opt/server/settings.xml
     ---> Using cache
     ---> 9b0964db2c9f
    Step 5/9 : WORKDIR  /opt/server/
     ---> Using cache
     ---> 542d0bd9d12f
    Step 6/9 : RUN mvn -s settings.xml -B -e -C -T 1C org.apache.maven.plugins:maven-dependency-plugin:3.0.2:go-offline
     ---> Using cache
     ---> 3c2d8df6b52e
    Step 7/9 : ADD ./src /opt/server/src
     ---> Using cache
     ---> 6d48dd3f9f85
    Step 8/9 : RUN mvn -s settings.xml install -P embedded -Dmaven.test.skip=true -Dmaven.javadoc.skip=true -B -e -o -T 1C verify
     ---> Using cache
     ---> 1c109d2026c4
    Step 9/9 : CMD mvn -s settings.xml -Dtest="!*IT,!*IntegrationTest" -P jacoco test
     ---> Using cache
     ---> 45eac3094ea4
    Successfully built 45eac3094ea4
    Successfully tagged generic-dockerfile:tests
    

    Then you can execute the tests:

    $ docker run -ti generic-dockerfile:tests
    [INFO] Scanning for projects...
    [INFO]
    [INFO] ------------------------------------------------------------------------
    [INFO] Building spring-cloud-config-server 1.1.6-SNAPSHOT
    [INFO] ------------------------------------------------------------------------
    
    • You can also override the CMD arguments to run the integration tests as documented in the Dockerfile above.

    Build and Run app

    You can build the runtime image as usual

    $ docker build -t generic-dockerfile .
    
    done.
    done.
    Removing intermediate container e632d7c310f7
     ---> e9391a0ca21d
    Step 16/16 : ENTRYPOINT sh -c "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom            $JAVA_PARAMS -jar /runtime/server.jar --server.port=8888 $SPRING_BOOT_APP_OPTS"
     ---> Running in 849ba7ad3212
    Removing intermediate container 849ba7ad3212
     ---> 909354984264
    Successfully built 909354984264
    Successfully tagged generic-dockerfile:latest
    

    Running the application is as simple as the following

    $ docker run -ti generic-dockerfile
    ThisHost: getLocalHost says localHost="c34b2cedbebf/172.17.0.2" isLoopbackAddress=false
       2018-04-19T19:15:41,180 3166  | INFO  | internal.util.Version.<clinit>#30 ["background-preinit" {}] HV000001: Hibernate Validator 5.2.5.Final
       2018-04-19T19:15:41,470 3456  | INFO  | factory.annotation.AutowiredAnnotationBeanPostProcessor.<init>#155 ["main" {svr=c34b2cedbebf}] JSR-330 'javax.inject.Inject' annotation found and supported for autowiring